fc0b4a8c5789be255d1781619db7664332f28ed7
[WebKit-https.git] / Source / WebCore / editing / AlternativeTextController.cpp
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 "AlternativeTextController.h"
29
30 #include "DictationAlternative.h"
31 #include "Document.h"
32 #include "DocumentMarkerController.h"
33 #include "EditCommand.h"
34 #include "Editor.h"
35 #include "EditorClient.h"
36 #include "Element.h"
37 #include "Event.h"
38 #include "ExceptionCodePlaceholder.h"
39 #include "FloatQuad.h"
40 #include "Frame.h"
41 #include "FrameView.h"
42 #include "Page.h"
43 #include "SpellingCorrectionCommand.h"
44 #include "TextCheckerClient.h"
45 #include "TextCheckingHelper.h"
46 #include "TextEvent.h"
47 #include "TextIterator.h"
48 #include "VisibleSelection.h"
49 #include "VisibleUnits.h"
50 #include "htmlediting.h"
51 #include "markup.h"
52
53 namespace WebCore {
54
55 using namespace std;
56 using namespace WTF;
57
58 class AutocorrectionAlternativeDetails : public AlternativeTextDetails {
59 public:
60     static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString)
61     {
62         return adoptRef(new AutocorrectionAlternativeDetails(replacementString));
63     }
64     
65     const String& replacementString() const { return m_replacementString; }
66 private:
67     AutocorrectionAlternativeDetails(const String& replacementString)
68     : m_replacementString(replacementString)
69     { }
70     
71     String m_replacementString;
72 };
73
74 class DictationAlternativeDetails : public AlternativeTextDetails {
75 public:
76     static PassRefPtr<DictationAlternativeDetails> create(uint64_t dictationContext)
77     {
78         return adoptRef(new DictationAlternativeDetails(dictationContext));
79     }
80
81     uint64_t dictationContext() const { return m_dictationContext; }
82
83 private:
84     DictationAlternativeDetails(uint64_t dictationContext)
85     : m_dictationContext(dictationContext)
86     { }
87
88     uint64_t m_dictationContext;
89 };
90
91 #if USE(AUTOCORRECTION_PANEL)
92
93 static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
94 {
95     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
96     if (markerTypesForAutoCorrection.isEmpty()) {
97         markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
98         markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
99         markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
100         markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
101     }
102     return markerTypesForAutoCorrection;
103 }
104
105 static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
106 {
107     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
108     if (markerTypesForReplacement.isEmpty()) {
109         markerTypesForReplacement.append(DocumentMarker::Replacement);
110         markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
111     }
112     return markerTypesForReplacement;
113 }
114
115 static const Vector<DocumentMarker::MarkerType>& markerTypesForAppliedDictationAlternative()
116 {
117     DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAppliedDictationAlternative, ());
118     if (markerTypesForAppliedDictationAlternative.isEmpty())
119         markerTypesForAppliedDictationAlternative.append(DocumentMarker::SpellCheckingExemption);
120     return markerTypesForAppliedDictationAlternative;
121 }
122
123 static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& markers)
124 {
125     if (markers.isEmpty())
126         return true;
127
128     const String& description = markers[0]->description();
129     for (size_t i = 1; i < markers.size(); ++i) {
130         if (description != markers[i]->description())
131             return false;
132     }
133     return true;
134 }
135
136 AlternativeTextController::AlternativeTextController(Frame& frame)
137     : m_timer(this, &AlternativeTextController::timerFired)
138     , m_frame(frame)
139 {
140 }
141
142 AlternativeTextController::~AlternativeTextController()
143 {
144     dismiss(ReasonForDismissingAlternativeTextIgnored);
145 }
146
147 void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType type)
148 {
149     const double correctionPanelTimerInterval = 0.3;
150     if (!isAutomaticSpellingCorrectionEnabled())
151         return;
152
153     // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
154     if (type == AlternativeTextTypeCorrection)
155         m_alternativeTextInfo.rangeWithAlternative.clear();
156     m_alternativeTextInfo.type = type;
157     m_timer.startOneShot(correctionPanelTimerInterval);
158 }
159
160 void AlternativeTextController::stopAlternativeTextUITimer()
161 {
162     m_timer.stop();
163     m_alternativeTextInfo.rangeWithAlternative.clear();
164 }
165
166 void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection)
167 {
168     // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
169     VisibleSelection currentSelection(m_frame.selection().selection());
170     if (currentSelection == oldSelection)
171         return;
172
173     stopAlternativeTextUITimer();
174     dismiss(ReasonForDismissingAlternativeTextIgnored);
175 }
176
177 void AlternativeTextController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
178 {
179     // Apply pending autocorrection before next round of spell checking.
180     bool doApplyCorrection = true;
181     VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
182     VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
183     if (currentWord.visibleEnd() == startOfSelection) {
184         String wordText = plainText(currentWord.toNormalizedRange().get());
185         if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
186             doApplyCorrection = false;
187     }
188     if (doApplyCorrection)
189         handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); 
190     else
191         m_alternativeTextInfo.rangeWithAlternative.clear();
192 }
193
194 bool AlternativeTextController::hasPendingCorrection() const
195 {
196     return m_alternativeTextInfo.rangeWithAlternative;
197 }
198
199 bool AlternativeTextController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
200 {
201     return !m_frame.document()->markers().hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
202 }
203
204 void AlternativeTextController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
205 {
206     FloatRect boundingBox = rootViewRectForRange(rangeToReplace.get());
207     if (boundingBox.isEmpty())
208         return;
209     m_alternativeTextInfo.originalText = plainText(rangeToReplace.get());
210     m_alternativeTextInfo.rangeWithAlternative = rangeToReplace;
211     m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(replacement);
212     m_alternativeTextInfo.isActive = true;
213     if (AlternativeTextClient* client = alternativeTextClient())
214         client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, replacement, Vector<String>());
215 }
216
217 void AlternativeTextController::handleCancelOperation()
218 {
219     if (!m_alternativeTextInfo.isActive)
220         return;
221     m_alternativeTextInfo.isActive = false;
222     dismiss(ReasonForDismissingAlternativeTextCancelled);
223 }
224
225 void AlternativeTextController::dismiss(ReasonForDismissingAlternativeText reasonForDismissing)
226 {
227     if (!m_alternativeTextInfo.isActive)
228         return;
229     m_alternativeTextInfo.isActive = false;
230     m_isDismissedByEditing = true;
231     if (AlternativeTextClient* client = alternativeTextClient())
232         client->dismissAlternative(reasonForDismissing);
233 }
234
235 String AlternativeTextController::dismissSoon(ReasonForDismissingAlternativeText reasonForDismissing)
236 {
237     if (!m_alternativeTextInfo.isActive)
238         return String();
239     m_alternativeTextInfo.isActive = false;
240     m_isDismissedByEditing = true;
241     if (AlternativeTextClient* client = alternativeTextClient())
242         return client->dismissAlternativeSoon(reasonForDismissing);
243     return String();
244 }
245
246 void AlternativeTextController::applyAlternativeTextToRange(const Range* range, const String& alternative, AlternativeTextType alternativeType, const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
247 {
248     if (!range)
249         return;
250
251     ExceptionCode ec = 0;
252     RefPtr<Range> paragraphRangeContainingCorrection = range->cloneRange(ec);
253     if (ec)
254         return;
255
256     setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(range->startPosition()));
257     setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(range->endPosition()));
258
259     // After we replace the word at range rangeWithAlternative, we need to add markers to that range.
260     // However, once the replacement took place, the value of rangeWithAlternative is not valid anymore.
261     // So before we carry out the replacement, we need to store the start position of rangeWithAlternative
262     // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph
263     // to store this value. In order to obtain this offset, we need to first create a range
264     // which spans from the start of paragraph to the start position of rangeWithAlternative.
265     RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(&paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
266     if (ec)
267         return;
268
269     Position startPositionOfrangeWithAlternative = range->startPosition();
270     correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec);
271     if (ec)
272         return;
273
274     // Take note of the location of autocorrection so that we can add marker after the replacement took place.
275     int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get());
276
277     // Clone the range, since the caller of this method may want to keep the original range around.
278     RefPtr<Range> rangeWithAlternative = range->cloneRange(ec);
279     
280     int paragraphStartIndex = TextIterator::rangeLength(Range::create(m_frame.document(), m_frame.document(), 0, paragraphRangeContainingCorrection.get()->startContainer(), paragraphRangeContainingCorrection.get()->startOffset()).get());
281     applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative, alternative));
282     // Recalculate pragraphRangeContainingCorrection, since SpellingCorrectionCommand modified the DOM, such that the original paragraphRangeContainingCorrection is no longer valid. Radar: 10305315 Bugzilla: 89526
283     paragraphRangeContainingCorrection = TextIterator::rangeFromLocationAndLength(m_frame.document(), paragraphStartIndex, correctionStartOffsetInParagraph + alternative.length());
284     
285     setEnd(paragraphRangeContainingCorrection.get(), m_frame.selection().selection().start());
286     RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, alternative.length());
287     String newText = plainText(replacementRange.get());
288
289     // Check to see if replacement succeeded.
290     if (newText != alternative)
291         return;
292
293     DocumentMarkerController& markers = replacementRange->startContainer()->document().markers();
294     size_t size = markerTypesToAdd.size();
295     for (size_t i = 0; i < size; ++i)
296         markers.addMarker(replacementRange.get(), markerTypesToAdd[i], markerDescriptionForAppliedAlternativeText(alternativeType, markerTypesToAdd[i]));
297 }
298
299 bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate()
300 {
301     if (!m_alternativeTextInfo.rangeWithAlternative || !m_alternativeTextInfo.isActive)
302         return false;
303
304     if (m_alternativeTextInfo.type != AlternativeTextTypeCorrection)
305         return false;
306
307     Position caretPosition = m_frame.selection().selection().start();
308
309     if (m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition) {
310         handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted));
311         return true;
312     } 
313     
314     // 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.
315     ASSERT(m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition);
316     dismiss(ReasonForDismissingAlternativeTextIgnored);
317     return false;
318 }
319
320 void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
321 {
322     if (AlternativeTextClient* client = alternativeTextClient())
323         client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction);
324     m_frame.document()->updateLayout();
325     m_frame.selection().setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered);
326     RefPtr<Range> range = Range::create(m_frame.document(), m_frame.selection().selection().start(), m_frame.selection().selection().end());
327
328     DocumentMarkerController& markers = m_frame.document()->markers();
329     markers.removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
330     markers.addMarker(range.get(), DocumentMarker::Replacement);
331     markers.addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
332 }
333
334 void AlternativeTextController::timerFired(Timer<AlternativeTextController>*)
335 {
336     m_isDismissedByEditing = false;
337     switch (m_alternativeTextInfo.type) {
338     case AlternativeTextTypeCorrection: {
339         VisibleSelection selection(m_frame.selection().selection());
340         VisiblePosition start(selection.start(), selection.affinity());
341         VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
342         VisibleSelection adjacentWords = VisibleSelection(p, start);
343         m_frame.editor().markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeReplacement | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
344     }
345         break;
346     case AlternativeTextTypeReversion: {
347         if (!m_alternativeTextInfo.rangeWithAlternative)
348             break;
349         m_alternativeTextInfo.isActive = true;
350         m_alternativeTextInfo.originalText = plainText(m_alternativeTextInfo.rangeWithAlternative.get());
351         FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get());
352         if (!boundingBox.isEmpty()) {
353             const AutocorrectionAlternativeDetails* details = static_cast<const AutocorrectionAlternativeDetails*>(m_alternativeTextInfo.details.get());
354             if (AlternativeTextClient* client = alternativeTextClient())
355                 client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, details->replacementString(), Vector<String>());
356         }
357     }
358         break;
359     case AlternativeTextTypeSpellingSuggestions: {
360         if (!m_alternativeTextInfo.rangeWithAlternative || plainText(m_alternativeTextInfo.rangeWithAlternative.get()) != m_alternativeTextInfo.originalText)
361             break;
362         String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get());
363         Vector<String> suggestions;
364         textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, suggestions);
365         if (suggestions.isEmpty()) {
366             m_alternativeTextInfo.rangeWithAlternative.clear();
367             break;
368         }
369         String topSuggestion = suggestions.first();
370         suggestions.remove(0);
371         m_alternativeTextInfo.isActive = true;
372         FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get());
373         if (!boundingBox.isEmpty()) {
374             if (AlternativeTextClient* client = alternativeTextClient())
375                 client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, topSuggestion, suggestions);
376         }
377     }
378         break;
379     case AlternativeTextTypeDictationAlternatives:
380     {
381 #if USE(DICTATION_ALTERNATIVES)
382         const Range* rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative.get();
383         const DictationAlternativeDetails* details = static_cast<const DictationAlternativeDetails*>(m_alternativeTextInfo.details.get());
384         if (!rangeWithAlternative || !details || !details->dictationContext())
385             return;
386         FloatRect boundingBox = rootViewRectForRange(rangeWithAlternative);
387         m_alternativeTextInfo.isActive = true;
388         if (!boundingBox.isEmpty()) {
389             if (AlternativeTextClient* client = alternativeTextClient())
390                 client->showDictationAlternativeUI(boundingBox, details->dictationContext());
391         }
392 #endif
393     }
394         break;
395     }
396 }
397
398 void AlternativeTextController::handleAlternativeTextUIResult(const String& result)
399 {
400     Range* rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative.get();
401     if (!rangeWithAlternative || m_frame.document() != &rangeWithAlternative->ownerDocument())
402         return;
403
404     String currentWord = plainText(rangeWithAlternative);
405     // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
406     if (currentWord != m_alternativeTextInfo.originalText)
407         return;
408
409     m_alternativeTextInfo.isActive = false;
410
411     switch (m_alternativeTextInfo.type) {
412     case AlternativeTextTypeCorrection:
413         if (result.length())
414             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAutocorrection());
415         else if (!m_isDismissedByEditing)
416             rangeWithAlternative->startContainer()->document().markers().addMarker(rangeWithAlternative, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText);
417         break;
418     case AlternativeTextTypeReversion:
419     case AlternativeTextTypeSpellingSuggestions:
420         if (result.length())
421             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForReplacement());
422         break;
423     case AlternativeTextTypeDictationAlternatives:
424         if (result.length())
425             applyAlternativeTextToRange(rangeWithAlternative, result, m_alternativeTextInfo.type, markerTypesForAppliedDictationAlternative());
426         break;
427     }
428
429     m_alternativeTextInfo.rangeWithAlternative.clear();
430 }
431
432 bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled()
433 {
434     return editorClient() && editorClient()->isAutomaticSpellingCorrectionEnabled();
435 }
436
437 FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) const
438 {
439     FrameView* view = m_frame.view();
440     if (!view)
441         return FloatRect();
442     Vector<FloatQuad> textQuads;
443     range->textQuads(textQuads);
444     FloatRect boundingRect;
445     size_t size = textQuads.size();
446     for (size_t i = 0; i < size; ++i)
447         boundingRect.unite(textQuads[i].boundingBox());
448     return view->contentsToRootView(IntRect(boundingRect));
449 }        
450
451 void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options)
452 {
453     VisibleSelection currentSelection(m_frame.selection().selection());
454     // When user moves caret to the end of autocorrected word and pauses, we show the panel
455     // containing the original pre-correction word so that user can quickly revert the
456     // undesired autocorrection. Here, we start correction panel timer once we confirm that
457     // the new caret position is at the end of a word.
458     if (!currentSelection.isCaret() || currentSelection == oldSelection || !currentSelection.isContentEditable())
459         return;
460
461     VisiblePosition selectionPosition = currentSelection.start();
462     
463     // Creating a Visible position triggers a layout and there is no
464     // guarantee that the selection is still valid.
465     if (selectionPosition.isNull())
466         return;
467     
468     VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary);
469     if (selectionPosition != endPositionOfWord)
470         return;
471
472     Position position = endPositionOfWord.deepEquivalent();
473     if (position.anchorType() != Position::PositionIsOffsetInAnchor)
474         return;
475
476     Node* node = position.containerNode();
477     Vector<DocumentMarker*> markers = node->document().markers().markersFor(node);
478     size_t markerCount = markers.size();
479     for (size_t i = 0; i < markerCount; ++i) {
480         const DocumentMarker* marker = markers[i];
481         if (!marker)
482             continue;
483
484         if (respondToMarkerAtEndOfWord(*marker, position, options))
485             break;
486     }
487 }
488
489 void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* command)
490 {
491     if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
492         m_frame.document()->markers().removeMarkers(DocumentMarker::CorrectionIndicator);
493
494     markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(command);
495     m_originalStringForLastDeletedAutocorrection = String();
496 }
497
498 void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command)
499 {
500     if (!command->wasCreateLinkCommand())
501         return;
502     RefPtr<Range> range = Range::create(m_frame.document(), command->startingSelection().start(), command->startingSelection().end());
503     if (!range)
504         return;
505     DocumentMarkerController& markers = m_frame.document()->markers();
506     markers.addMarker(range.get(), DocumentMarker::Replacement);
507     markers.addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
508 }
509
510 AlternativeTextClient* AlternativeTextController::alternativeTextClient()
511 {
512     return m_frame.page() ? m_frame.page()->alternativeTextClient() : 0;
513 }
514
515 EditorClient* AlternativeTextController::editorClient()
516 {
517     return m_frame.page() ? m_frame.page()->editorClient() : 0;
518 }
519
520 TextCheckerClient* AlternativeTextController::textChecker()
521 {
522     if (EditorClient* owner = editorClient())
523         return owner->textChecker();
524     return 0;
525 }
526
527 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
528 {
529     if (AlternativeTextClient* client = alternativeTextClient())
530         client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString);
531 }
532
533 void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
534 {
535     recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
536 }
537
538 void AlternativeTextController::markReversed(PassRefPtr<Range> changedRange)
539 {
540     changedRange->startContainer()->document().markers().removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
541     changedRange->startContainer()->document().markers().addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
542 }
543
544 void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
545 {
546     Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
547     DocumentMarkerController& markers = replacedRange->startContainer()->document().markers();
548     for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
549         DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
550         if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
551             markers.addMarker(replacedRange.get(), markerType, replacedString);
552         else
553             markers.addMarker(replacedRange.get(), markerType);
554     }
555 }
556
557 void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
558 {
559     if (!rangeOfCorrection)
560         return;
561     DocumentMarkerController& markers = rangeOfCorrection->startContainer()->document().markers();
562     Vector<DocumentMarker*> correctedOnceMarkers = markers.markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
563     if (correctedOnceMarkers.isEmpty())
564         return;
565
566     if (AlternativeTextClient* client = alternativeTextClient()) {
567         // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
568         // edited it to something else, and notify spellchecker accordingly.
569         if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected)
570             client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction);
571         else
572             client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction);
573     }
574
575     markers.removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
576 }
577
578 void AlternativeTextController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
579 {
580     m_originalStringForLastDeletedAutocorrection = originalString;
581     m_positionForLastDeletedAutocorrection = position;
582 }
583
584 void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command)
585 {
586     Position endOfSelection = command->endingSelection().end();
587     if (endOfSelection != m_positionForLastDeletedAutocorrection)
588         return;
589
590     Position precedingCharacterPosition = endOfSelection.previous();
591     if (endOfSelection == precedingCharacterPosition)
592         return;
593
594     RefPtr<Range> precedingCharacterRange = Range::create(m_frame.document(), precedingCharacterPosition, endOfSelection);
595     String string = plainText(precedingCharacterRange.get());
596     if (string.isEmpty() || !isWhitespace(string[string.length() - 1]))
597         return;
598
599     // Mark this whitespace to indicate we have deleted an autocorrection following this
600     // whitespace. So if the user types the same original word again at this position, we
601     // won't autocorrect it again.
602     m_frame.document()->markers().addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection);
603 }
604
605 bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeWithAlternative, const String& stringToBeReplaced)
606 {
607     DocumentMarkerController& markerController = m_frame.document()->markers();
608     if (markerController.hasMarkers(rangeWithAlternative, DocumentMarker::Replacement)) {
609         if (result->type == TextCheckingTypeCorrection)
610             recordSpellcheckerResponseForModifiedCorrection(rangeWithAlternative, stringToBeReplaced, result->replacement);
611         return false;
612     }
613
614     if (markerController.hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection))
615         return false;
616
617     Position beginningOfRange = rangeWithAlternative->startPosition();
618     Position precedingCharacterPosition = beginningOfRange.previous();
619     RefPtr<Range> precedingCharacterRange = Range::create(m_frame.document(), precedingCharacterPosition, beginningOfRange);
620
621     Vector<DocumentMarker*> markers = markerController.markersInRange(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection);
622
623     for (size_t i = 0; i < markers.size(); ++i) {
624         if (markers[i]->description() == stringToBeReplaced)
625             return false;
626     }
627
628     return true;
629 }
630
631 bool AlternativeTextController::shouldStartTimerFor(const WebCore::DocumentMarker &marker, int endOffset) const
632 {
633     return (((marker.type() == DocumentMarker::Replacement && !marker.description().isNull()) || marker.type() == DocumentMarker::Spelling || marker.type() == DocumentMarker::DictationAlternatives) && static_cast<int>(marker.endOffset()) == endOffset);
634 }
635
636 bool AlternativeTextController::respondToMarkerAtEndOfWord(const DocumentMarker& marker, const Position& endOfWordPosition, FrameSelection::SetSelectionOptions options)
637 {
638     if (options & FrameSelection::DictationTriggered)
639         return false;
640     if (!shouldStartTimerFor(marker, endOfWordPosition.offsetInContainerNode()))
641         return false;
642     Node* node = endOfWordPosition.containerNode();
643     RefPtr<Range> wordRange = Range::create(m_frame.document(), node, marker.startOffset(), node, marker.endOffset());
644     if (!wordRange)
645         return false;
646     String currentWord = plainText(wordRange.get());
647     if (!currentWord.length())
648         return false;
649     m_alternativeTextInfo.originalText = currentWord;
650     switch (marker.type()) {
651     case DocumentMarker::Spelling:
652         m_alternativeTextInfo.rangeWithAlternative = wordRange;
653         m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create("");
654         startAlternativeTextUITimer(AlternativeTextTypeSpellingSuggestions);
655         break;
656     case DocumentMarker::Replacement:
657         m_alternativeTextInfo.rangeWithAlternative = wordRange;
658         m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(marker.description());
659         startAlternativeTextUITimer(AlternativeTextTypeReversion);
660         break;
661     case DocumentMarker::DictationAlternatives: {
662         const DictationMarkerDetails* markerDetails = static_cast<const DictationMarkerDetails*>(marker.details());
663         if (!markerDetails)
664             return false;
665         if (currentWord != markerDetails->originalText())
666             return false;
667         m_alternativeTextInfo.rangeWithAlternative = wordRange;
668         m_alternativeTextInfo.details = DictationAlternativeDetails::create(markerDetails->dictationContext());
669         startAlternativeTextUITimer(AlternativeTextTypeDictationAlternatives);
670     }
671         break;
672     default:
673         ASSERT_NOT_REACHED();
674         break;
675     }
676     return true;
677 }
678
679 String AlternativeTextController::markerDescriptionForAppliedAlternativeText(AlternativeTextType alternativeTextType, DocumentMarker::MarkerType markerType)
680 {
681
682     if (alternativeTextType != AlternativeTextTypeReversion && alternativeTextType != AlternativeTextTypeDictationAlternatives && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
683         return m_alternativeTextInfo.originalText;
684     return "";
685 }
686
687 #endif
688
689 bool AlternativeTextController::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent)
690 {
691     EventTarget* target;
692     if (triggeringEvent)
693         target = triggeringEvent->target();
694     else
695         target = eventTargetElementForDocument(m_frame.document());
696     if (!target)
697         return false;
698
699     if (FrameView* view = m_frame.view())
700         view->resetDeferredRepaintDelay();
701
702     RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame.document()->domWindow(), text, dictationAlternatives);
703     event->setUnderlyingEvent(triggeringEvent);
704
705     target->dispatchEvent(event, IGNORE_EXCEPTION);
706     return event->defaultHandled();
707 }
708
709 void AlternativeTextController::removeDictationAlternativesForMarker(const DocumentMarker* marker)
710 {
711 #if USE(DICTATION_ALTERNATIVES)
712     DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details());
713     if (AlternativeTextClient* client = alternativeTextClient())
714         client->removeDictationAlternatives(details->dictationContext());
715 #else
716     UNUSED_PARAM(marker);
717 #endif
718 }
719
720 Vector<String> AlternativeTextController::dictationAlternativesForMarker(const DocumentMarker* marker)
721 {
722 #if USE(DICTATION_ALTERNATIVES)
723     ASSERT(marker->type() == DocumentMarker::DictationAlternatives);
724     if (AlternativeTextClient* client = alternativeTextClient()) {
725         DictationMarkerDetails* details = static_cast<DictationMarkerDetails*>(marker->details());
726         return client->dictationAlternatives(details->dictationContext());
727     }
728     return Vector<String>();
729 #else
730     UNUSED_PARAM(marker);
731     return Vector<String>();
732 #endif
733 }
734
735 void AlternativeTextController::applyDictationAlternative(const String& alternativeString)
736 {
737 #if USE(DICTATION_ALTERNATIVES)
738     Editor& editor = m_frame.editor();
739     RefPtr<Range> selection = editor.selectedRange();
740     if (!selection || !editor.shouldInsertText(alternativeString, selection.get(), EditorInsertActionPasted))
741         return;
742     DocumentMarkerController& markers = selection->startContainer()->document().markers();
743     Vector<DocumentMarker*> dictationAlternativesMarkers = markers.markersInRange(selection.get(), DocumentMarker::DictationAlternatives);
744     for (size_t i = 0; i < dictationAlternativesMarkers.size(); ++i)
745         removeDictationAlternativesForMarker(dictationAlternativesMarkers[i]);
746
747     applyAlternativeTextToRange(selection.get(), alternativeString, AlternativeTextTypeDictationAlternatives, markerTypesForAppliedDictationAlternative());
748 #else
749     UNUSED_PARAM(alternativeString);
750 #endif
751 }
752
753 } // namespace WebCore