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