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