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