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