67063ee1562ae31f22025b358511fdf4d998a571
[WebKit-https.git] / Source / WebCore / editing / TypingCommand.cpp
1 /*
2  * Copyright (C) 2005-2008, 2016 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "TypingCommand.h"
28
29 #include "AXObjectCache.h"
30 #include "BreakBlockquoteCommand.h"
31 #include "DataTransfer.h"
32 #include "DeleteSelectionCommand.h"
33 #include "Document.h"
34 #include "Editing.h"
35 #include "Editor.h"
36 #include "Element.h"
37 #include "Frame.h"
38 #include "HTMLElement.h"
39 #include "HTMLNames.h"
40 #include "InsertLineBreakCommand.h"
41 #include "InsertParagraphSeparatorCommand.h"
42 #include "InsertTextCommand.h"
43 #include "Logging.h"
44 #include "MarkupAccumulator.h"
45 #include "MathMLElement.h"
46 #include "RenderElement.h"
47 #include "StaticRange.h"
48 #include "TextIterator.h"
49 #include "VisibleUnits.h"
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 class TypingCommandLineOperation
56 {
57 public:
58     TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text)
59     : m_typingCommand(typingCommand)
60     , m_selectInsertedText(selectInsertedText)
61     , m_text(text)
62     { }
63     
64     void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const
65     {
66         if (isLastLine) {
67             if (!lineOffset || lineLength > 0)
68                 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText);
69         } else {
70             if (lineLength > 0)
71                 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false);
72             m_typingCommand->insertParagraphSeparator();
73         }
74     }
75     
76 private:
77     TypingCommand* m_typingCommand;
78     bool m_selectInsertedText;
79     const String& m_text;
80 };
81
82 static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity, TypingCommand::TextCompositionType compositionType, bool isAutocompletion)
83 {
84     if (compositionType == TypingCommand::TextCompositionPending) {
85         if (command == TypingCommand::InsertText)
86             return EditAction::TypingInsertPendingComposition;
87         if (command == TypingCommand::DeleteSelection)
88             return EditAction::TypingDeletePendingComposition;
89         ASSERT_NOT_REACHED();
90     }
91
92     if (compositionType == TypingCommand::TextCompositionFinal) {
93         if (command == TypingCommand::InsertText)
94             return EditAction::TypingInsertFinalComposition;
95         if (command == TypingCommand::DeleteSelection)
96             return EditAction::TypingDeleteFinalComposition;
97         ASSERT_NOT_REACHED();
98     }
99
100     switch (command) {
101     case TypingCommand::DeleteSelection:
102         return EditAction::TypingDeleteSelection;
103     case TypingCommand::DeleteKey: {
104         if (granularity == WordGranularity)
105             return EditAction::TypingDeleteWordBackward;
106         if (granularity == LineBoundary)
107             return EditAction::TypingDeleteLineBackward;
108         return EditAction::TypingDeleteBackward;
109     }
110     case TypingCommand::ForwardDeleteKey:
111         if (granularity == WordGranularity)
112             return EditAction::TypingDeleteWordForward;
113         if (granularity == LineBoundary)
114             return EditAction::TypingDeleteLineForward;
115         return EditAction::TypingDeleteForward;
116     case TypingCommand::InsertText:
117         return isAutocompletion ? EditAction::InsertReplacement : EditAction::TypingInsertText;
118     case TypingCommand::InsertLineBreak:
119         return EditAction::TypingInsertLineBreak;
120     case TypingCommand::InsertParagraphSeparator:
121     case TypingCommand::InsertParagraphSeparatorInQuotedContent:
122         return EditAction::TypingInsertParagraph;
123     default:
124         return EditAction::Unspecified;
125     }
126 }
127
128 static inline bool editActionIsDeleteByTyping(EditAction action)
129 {
130     switch (action) {
131     case EditAction::TypingDeleteSelection:
132     case EditAction::TypingDeleteBackward:
133     case EditAction::TypingDeleteWordBackward:
134     case EditAction::TypingDeleteLineBackward:
135     case EditAction::TypingDeleteForward:
136     case EditAction::TypingDeleteWordForward:
137     case EditAction::TypingDeleteLineForward:
138         return true;
139     default:
140         return false;
141     }
142 }
143
144 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
145     : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity, compositionType, options & IsAutocompletion))
146     , m_commandType(commandType)
147     , m_textToInsert(textToInsert)
148     , m_currentTextToInsert(textToInsert)
149     , m_openForMoreTyping(true)
150     , m_selectInsertedText(options & SelectInsertedText)
151     , m_smartDelete(options & SmartDelete)
152     , m_granularity(granularity)
153     , m_compositionType(compositionType)
154     , m_shouldAddToKillRing(options & AddsToKillRing)
155     , m_isAutocompletion(options & IsAutocompletion)
156     , m_openedByBackwardDelete(false)
157     , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
158     , m_shouldPreventSpellChecking(options & PreventSpellChecking)
159 {
160     m_currentTypingEditAction = editingAction();
161     updatePreservesTypingStyle(m_commandType);
162 }
163
164 void TypingCommand::deleteSelection(Document& document, Options options, TextCompositionType compositionType)
165 {
166     Frame* frame = document.frame();
167     ASSERT(frame);
168
169     if (!frame->selection().isRange())
170         return;
171
172     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
173         lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
174         lastTypingCommand->setCompositionType(compositionType);
175         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
176         lastTypingCommand->deleteSelection(options & SmartDelete);
177         return;
178     }
179
180     TypingCommand::create(document, DeleteSelection, emptyString(), options, compositionType)->apply();
181 }
182
183 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity)
184 {
185     if (granularity == CharacterGranularity) {
186         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
187             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document.frame());
188             lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
189             lastTypingCommand->setCompositionType(TextCompositionNone);
190             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
191             lastTypingCommand->deleteKeyPressed(granularity, options & AddsToKillRing);
192             return;
193         }
194     }
195
196     TypingCommand::create(document, DeleteKey, emptyString(), options, granularity)->apply();
197 }
198
199 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity)
200 {
201     // FIXME: Forward delete in TextEdit appears to open and close a new typing command.
202     Frame* frame = document.frame();
203     if (granularity == CharacterGranularity) {
204         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
205             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
206             lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
207             lastTypingCommand->setCompositionType(TextCompositionNone);
208             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
209             lastTypingCommand->forwardDeleteKeyPressed(granularity, options & AddsToKillRing);
210             return;
211         }
212     }
213
214     TypingCommand::create(document, ForwardDeleteKey, emptyString(), options, granularity)->apply();
215 }
216
217 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame)
218 {
219     ASSERT(frame);
220     VisibleSelection currentSelection = frame->selection().selection();
221     if (currentSelection == typingCommand->endingSelection())
222         return;
223
224     typingCommand->setStartingSelection(currentSelection);
225     typingCommand->setEndingSelection(currentSelection);
226 }
227
228 void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition)
229 {
230     Frame* frame = document.frame();
231     ASSERT(frame);
232
233     if (!text.isEmpty())
234         frame->editor().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]));
235     
236     insertText(document, text, frame->selection().selection(), options, composition);
237 }
238
239 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection.
240 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
241 {
242     RefPtr<Frame> frame = document.frame();
243     ASSERT(frame);
244
245     LOG(Editing, "TypingCommand::insertText (text %s)", text.utf8().data());
246
247     VisibleSelection currentSelection = frame->selection().selection();
248
249     String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionPending);
250     
251     // Set the starting and ending selection appropriately if we are using a selection
252     // that is different from the current selection.  In the future, we should change EditCommand
253     // to deal with custom selections in a general way that can be used by all of the commands.
254     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
255         if (lastTypingCommand->endingSelection() != selectionForInsertion) {
256             lastTypingCommand->setStartingSelection(selectionForInsertion);
257             lastTypingCommand->setEndingSelection(selectionForInsertion);
258         }
259
260         lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
261         lastTypingCommand->setCompositionType(compositionType);
262         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
263         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
264         lastTypingCommand->insertTextAndNotifyAccessibility(newText, options & SelectInsertedText);
265         return;
266     }
267
268     auto cmd = TypingCommand::create(document, InsertText, newText, options, compositionType);
269     applyTextInsertionCommand(frame.get(), cmd.get(), selectionForInsertion, currentSelection);
270 }
271
272 void TypingCommand::insertLineBreak(Document& document, Options options)
273 {
274     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
275         lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
276         lastTypingCommand->setCompositionType(TextCompositionNone);
277         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
278         lastTypingCommand->insertLineBreakAndNotifyAccessibility();
279         return;
280     }
281
282     TypingCommand::create(document, InsertLineBreak, emptyString(), options)->apply();
283 }
284
285 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
286 {
287     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
288         lastTypingCommand->setIsAutocompletion(false);
289         lastTypingCommand->setCompositionType(TextCompositionNone);
290         lastTypingCommand->insertParagraphSeparatorInQuotedContentAndNotifyAccessibility();
291         return;
292     }
293
294     TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->apply();
295 }
296
297 void TypingCommand::insertParagraphSeparator(Document& document, Options options)
298 {
299     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
300         lastTypingCommand->setIsAutocompletion(options & IsAutocompletion);
301         lastTypingCommand->setCompositionType(TextCompositionNone);
302         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
303         lastTypingCommand->insertParagraphSeparatorAndNotifyAccessibility();
304         return;
305     }
306
307     TypingCommand::create(document, InsertParagraphSeparator, emptyString(), options)->apply();
308 }
309
310 RefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame& frame)
311 {
312     RefPtr<CompositeEditCommand> lastEditCommand = frame.editor().lastEditCommand();
313     if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping())
314         return nullptr;
315
316     return static_cast<TypingCommand*>(lastEditCommand.get());
317 }
318
319 bool TypingCommand::shouldDeferWillApplyCommandUntilAddingTypingCommand() const
320 {
321     return !m_isHandlingInitialTypingCommand || editActionIsDeleteByTyping(editingAction());
322 }
323
324 void TypingCommand::closeTyping(Frame* frame)
325 {
326     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame))
327         lastTypingCommand->closeTyping();
328 }
329
330 #if PLATFORM(IOS_FAMILY)
331 void TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(Frame* frame, const VisibleSelection& newSelection)
332 {
333     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
334         lastTypingCommand->setEndingSelection(newSelection);
335         lastTypingCommand->setEndingSelectionOnLastInsertCommand(newSelection);
336     }
337 }
338 #endif
339
340 void TypingCommand::postTextStateChangeNotificationForDeletion(const VisibleSelection& selection)
341 {
342     if (!AXObjectCache::accessibilityEnabled())
343         return;
344     postTextStateChangeNotification(AXTextEditTypeDelete, AccessibilityObject::stringForVisiblePositionRange(selection), selection.start());
345     VisiblePositionIndexRange range;
346     range.startIndex.value = indexForVisiblePosition(selection.start(), range.startIndex.scope);
347     range.endIndex.value = indexForVisiblePosition(selection.end(), range.endIndex.scope);
348     composition()->setRangeDeletedByUnapply(range);
349 }
350
351 bool TypingCommand::willApplyCommand()
352 {
353     if (shouldDeferWillApplyCommandUntilAddingTypingCommand()) {
354         // The TypingCommand will handle the willApplyCommand logic separately in TypingCommand::willAddTypingToOpenCommand.
355         return true;
356     }
357
358     return CompositeEditCommand::willApplyCommand();
359 }
360
361 void TypingCommand::doApply()
362 {
363     if (endingSelection().isNoneOrOrphaned())
364         return;
365
366     if (m_commandType == DeleteKey)
367         if (m_commands.isEmpty())
368             m_openedByBackwardDelete = true;
369
370     switch (m_commandType) {
371     case DeleteSelection:
372         deleteSelection(m_smartDelete);
373         return;
374     case DeleteKey:
375         deleteKeyPressed(m_granularity, m_shouldAddToKillRing);
376         return;
377     case ForwardDeleteKey:
378         forwardDeleteKeyPressed(m_granularity, m_shouldAddToKillRing);
379         return;
380     case InsertLineBreak:
381         insertLineBreakAndNotifyAccessibility();
382         return;
383     case InsertParagraphSeparator:
384         insertParagraphSeparatorAndNotifyAccessibility();
385         return;
386     case InsertParagraphSeparatorInQuotedContent:
387         insertParagraphSeparatorInQuotedContentAndNotifyAccessibility();
388         return;
389     case InsertText:
390         insertTextAndNotifyAccessibility(m_textToInsert, m_selectInsertedText);
391         return;
392     }
393
394     ASSERT_NOT_REACHED();
395 }
396
397 String TypingCommand::inputEventTypeName() const
398 {
399     return inputTypeNameForEditingAction(m_currentTypingEditAction);
400 }
401
402 bool TypingCommand::isBeforeInputEventCancelable() const
403 {
404     return m_currentTypingEditAction != EditAction::TypingInsertPendingComposition && m_currentTypingEditAction != EditAction::TypingDeletePendingComposition;
405 }
406
407 String TypingCommand::inputEventData() const
408 {
409     switch (m_currentTypingEditAction) {
410     case EditAction::TypingInsertText:
411     case EditAction::TypingInsertPendingComposition:
412     case EditAction::TypingInsertFinalComposition:
413         return m_currentTextToInsert;
414     case EditAction::InsertReplacement:
415         return isEditingTextAreaOrTextInput() ? m_currentTextToInsert : String();
416     default:
417         return CompositeEditCommand::inputEventData();
418     }
419 }
420
421 RefPtr<DataTransfer> TypingCommand::inputEventDataTransfer() const
422 {
423     if (m_currentTypingEditAction != EditAction::InsertReplacement || isEditingTextAreaOrTextInput())
424         return nullptr;
425
426     StringBuilder htmlText;
427     MarkupAccumulator::appendCharactersReplacingEntities(htmlText, m_currentTextToInsert, 0, m_currentTextToInsert.length(), EntityMaskInHTMLPCDATA);
428     return DataTransfer::createForInputEvent(m_currentTextToInsert, htmlText.toString());
429 }
430
431 void TypingCommand::didApplyCommand()
432 {
433     // TypingCommands handle applied editing separately (see TypingCommand::typingAddedToOpenCommand).
434     m_isHandlingInitialTypingCommand = false;
435 }
436
437 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
438 {
439     Frame& frame = this->frame();
440
441 #if PLATFORM(MAC)
442     if (!frame.editor().isContinuousSpellCheckingEnabled()
443         && !frame.editor().isAutomaticQuoteSubstitutionEnabled()
444         && !frame.editor().isAutomaticLinkDetectionEnabled()
445         && !frame.editor().isAutomaticDashSubstitutionEnabled()
446         && !frame.editor().isAutomaticTextReplacementEnabled())
447             return;
448     if (frame.editor().isHandlingAcceptedCandidate())
449         return;
450 #else
451     if (!frame.editor().isContinuousSpellCheckingEnabled())
452         return;
453 #endif
454     // Take a look at the selection that results after typing and determine whether we need to spellcheck. 
455     // Since the word containing the current selection is never marked, this does a check to
456     // see if typing made a new word that is not in the current selection. Basically, you
457     // get this by being at the end of a word and typing a space.    
458     VisiblePosition start(endingSelection().start(), endingSelection().affinity());
459     VisiblePosition previous = start.previous();
460     if (previous.isNotNull()) {
461 #if !PLATFORM(IOS_FAMILY)
462         VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
463         VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
464         if (p1 != p2) {
465             RefPtr<Range> range = makeRange(p1, p2);
466             String strippedPreviousWord;
467             if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent))
468                 strippedPreviousWord = plainText(range.get()).stripWhiteSpace();
469             frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty());
470         } else if (commandType == TypingCommand::InsertText)
471             frame.editor().startAlternativeTextUITimer();
472 #else
473         UNUSED_PARAM(commandType);
474         // If this bug gets fixed, this PLATFORM(IOS_FAMILY) code could be removed:
475         // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop
476         EWordSide startWordSide = LeftWordIfOnBoundary;
477         UChar32 c = previous.characterAfter();
478         // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same
479         // way as TextIterator, so we do an isEndOfParagraph check here.
480         if (isSpaceOrNewline(c) || c == noBreakSpace || isEndOfParagraph(previous)) {
481             startWordSide = RightWordIfOnBoundary;
482         }
483         VisiblePosition p1 = startOfWord(previous, startWordSide);
484         VisiblePosition p2 = startOfWord(start, startWordSide);
485         if (p1 != p2)
486             frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), false);
487 #endif // !PLATFORM(IOS_FAMILY)
488     }
489 }
490
491 bool TypingCommand::willAddTypingToOpenCommand(ETypingCommand commandType, TextGranularity granularity, const String& text, RefPtr<Range>&& range)
492 {
493     m_currentTextToInsert = text;
494     m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity, m_compositionType, m_isAutocompletion);
495
496     if (!shouldDeferWillApplyCommandUntilAddingTypingCommand())
497         return true;
498
499     if (!range || isEditingTextAreaOrTextInput())
500         return frame().editor().willApplyEditing(*this, CompositeEditCommand::targetRangesForBindings());
501
502     return frame().editor().willApplyEditing(*this, { 1, StaticRange::createFromRange(*range) });
503 }
504
505 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
506 {
507     Frame& frame = this->frame();
508
509     updatePreservesTypingStyle(commandTypeForAddedTyping);
510
511 #if PLATFORM(COCOA)
512     frame.editor().appliedEditing(*this);
513     // Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes.
514     if (!m_shouldPreventSpellChecking)
515         markMisspellingsAfterTyping(commandTypeForAddedTyping);
516 #else
517     // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
518     markMisspellingsAfterTyping(commandTypeForAddedTyping);
519     frame.editor().appliedEditing(*this);
520 #endif
521 }
522
523 void TypingCommand::insertText(const String &text, bool selectInsertedText)
524 {
525     // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
526     // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
527     // an existing selection; at the moment they can either put the caret after what's inserted or
528     // select what's inserted, but there's no way to "extend selection" to include both an old selection
529     // that ends just before where we want to insert text and the newly inserted text.
530     TypingCommandLineOperation operation(this, selectInsertedText, text);
531     forEachLineInString(text, operation);
532 }
533
534 void TypingCommand::insertTextAndNotifyAccessibility(const String &text, bool selectInsertedText)
535 {
536     LOG(Editing, "TypingCommand %p insertTextAndNotifyAccessibility (text %s, selectInsertedText %d)", this, text.utf8().data(), selectInsertedText);
537
538     AccessibilityReplacedText replacedText(frame().selection().selection());
539     insertText(text, selectInsertedText);
540     replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, text, frame().selection().selection());
541     composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
542 }
543
544 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
545 {
546     if (!willAddTypingToOpenCommand(InsertText, CharacterGranularity, text))
547         return;
548
549     auto command = InsertTextCommand::create(document(), text, selectInsertedText,
550         m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces, EditAction::TypingInsertText);
551
552     applyCommandToComposite(WTFMove(command), endingSelection());
553
554     typingAddedToOpenCommand(InsertText);
555 }
556
557 void TypingCommand::insertLineBreak()
558 {
559     if (!canAppendNewLineFeedToSelection(endingSelection()))
560         return;
561
562     if (!willAddTypingToOpenCommand(InsertLineBreak, LineGranularity))
563         return;
564
565     applyCommandToComposite(InsertLineBreakCommand::create(document()));
566     typingAddedToOpenCommand(InsertLineBreak);
567 }
568
569 void TypingCommand::insertLineBreakAndNotifyAccessibility()
570 {
571     AccessibilityReplacedText replacedText(frame().selection().selection());
572     insertLineBreak();
573     replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection());
574     composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
575 }
576
577 void TypingCommand::insertParagraphSeparator()
578 {
579     if (!canAppendNewLineFeedToSelection(endingSelection()))
580         return;
581
582     if (!willAddTypingToOpenCommand(InsertParagraphSeparator, ParagraphGranularity))
583         return;
584
585     applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), false, false, EditAction::TypingInsertParagraph));
586     typingAddedToOpenCommand(InsertParagraphSeparator);
587 }
588
589 void TypingCommand::insertParagraphSeparatorAndNotifyAccessibility()
590 {
591     AccessibilityReplacedText replacedText(frame().selection().selection());
592     insertParagraphSeparator();
593     replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection());
594     composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
595 }
596
597 void TypingCommand::insertParagraphSeparatorInQuotedContent()
598 {
599     if (!willAddTypingToOpenCommand(InsertParagraphSeparatorInQuotedContent, ParagraphGranularity))
600         return;
601
602     // If the selection starts inside a table, just insert the paragraph separator normally
603     // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
604     if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
605         insertParagraphSeparator();
606         return;
607     }
608         
609     applyCommandToComposite(BreakBlockquoteCommand::create(document()));
610     typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
611 }
612
613 void TypingCommand::insertParagraphSeparatorInQuotedContentAndNotifyAccessibility()
614 {
615     AccessibilityReplacedText replacedText(frame().selection().selection());
616     insertParagraphSeparatorInQuotedContent();
617     replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypeTyping, "\n", frame().selection().selection());
618     composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
619 }
620
621 bool TypingCommand::makeEditableRootEmpty()
622 {
623     Element* root = endingSelection().rootEditableElement();
624     if (!root || !root->firstChild())
625         return false;
626
627     if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) {
628         // If there is a single child and it could be a placeholder, leave it alone.
629         if (root->renderer() && root->renderer()->isRenderBlockFlow())
630             return false;
631     }
632
633     while (Node* child = root->firstChild())
634         removeNode(*child);
635
636     addBlockPlaceholderIfNeeded(root);
637     setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional()));
638
639     return true;
640 }
641
642 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing)
643 {
644     Frame& frame = this->frame();
645     Ref<Frame> protector(frame);
646
647     frame.editor().updateMarkersForWordsAffectedByEditing(false);
648
649     VisibleSelection selectionToDelete;
650     VisibleSelection selectionAfterUndo;
651     bool expandForSpecialElements = !endingSelection().isCaret();
652
653     switch (endingSelection().selectionType()) {
654     case VisibleSelection::RangeSelection:
655         selectionToDelete = endingSelection();
656         selectionAfterUndo = selectionToDelete;
657         break;
658     case VisibleSelection::CaretSelection: {
659         // After breaking out of an empty mail blockquote, we still want continue with the deletion
660         // so actual content will get deleted, and not just the quote style.
661         if (breakOutOfEmptyMailBlockquotedParagraph())
662             typingAddedToOpenCommand(DeleteKey);
663
664         m_smartDelete = false;
665
666         FrameSelection selection;
667         selection.setSelection(endingSelection());
668         selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
669         if (shouldAddToKillRing && selection.isCaret() && granularity != CharacterGranularity)
670             selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
671
672         const VisiblePosition& visibleStart = endingSelection().visibleStart();
673         const VisiblePosition& previousPosition = visibleStart.previous(CannotCrossEditingBoundary);
674         Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell);
675         const Node* enclosingTableCellForPreviousPosition = enclosingNodeOfType(previousPosition.deepEquivalent(), &isTableCell);
676         if (previousPosition.isNull() || enclosingTableCell != enclosingTableCellForPreviousPosition) {
677             // When the caret is at the start of the editable area in an empty list item, break out of the list item.
678             if (auto deleteListSelection = shouldBreakOutOfEmptyListItem()) {
679                 if (willAddTypingToOpenCommand(DeleteKey, granularity, { }, Range::create(document(), deleteListSelection.value().start(), deleteListSelection.value().end()))) {
680                     breakOutOfEmptyListItem();
681                     typingAddedToOpenCommand(DeleteKey);
682                 }
683                 return;
684             }
685         }
686         if (previousPosition.isNull()) {
687             // When there are no visible positions in the editing root, delete its entire contents.
688             // FIXME: Dispatch a `beforeinput` event here and bail if preventDefault() was invoked.
689             if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
690                 typingAddedToOpenCommand(DeleteKey);
691                 return;
692             }
693         }
694
695         // If we have a caret selection at the beginning of a cell, we have nothing to do.
696         if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell))
697             return;
698
699         // If the caret is at the start of a paragraph after a table, move content into the last table cell.
700         if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
701             // Unless the caret is just before a table.  We don't want to move a table into the last table cell.
702             if (isLastPositionBeforeTable(visibleStart))
703                 return;
704             // Extend the selection backward into the last cell, then deletion will handle the move.
705             selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
706         // If the caret is just after a table, select the table and don't delete anything.
707         } else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
708             setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional()));
709             typingAddedToOpenCommand(DeleteKey);
710             return;
711         }
712
713         selectionToDelete = selection.selection();
714
715         if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode()
716             && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) {
717             // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
718             selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
719         }
720
721         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
722             selectionAfterUndo = selectionToDelete;
723         else
724             // It's a little tricky to compute what the starting selection would have been in the original document.
725             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
726             // the current state of the document and we'll get the wrong result.
727             selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
728         break;
729     }
730     case VisibleSelection::NoSelection:
731         ASSERT_NOT_REACHED();
732         break;
733     }
734     
735     ASSERT(!selectionToDelete.isNone());
736     if (selectionToDelete.isNone()) {
737 #if PLATFORM(IOS_FAMILY)
738         // Workaround for this bug:
739         // <rdar://problem/4653755> UIKit text widgets should use WebKit editing API to manipulate text
740         setEndingSelection(frame.selection().selection());
741         closeTyping(&frame);
742 #endif
743         return;
744     }
745     
746     if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete))
747         return;
748     
749     if (!willAddTypingToOpenCommand(DeleteKey, granularity, { }, selectionToDelete.firstRange()))
750         return;
751
752     if (shouldAddToKillRing)
753         frame.editor().addRangeToKillRing(*selectionToDelete.toNormalizedRange().get(), Editor::KillRingInsertionMode::PrependText);
754
755     // Post the accessibility notification before actually deleting the content while selectionToDelete is still valid
756     postTextStateChangeNotificationForDeletion(selectionToDelete);
757
758     // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
759     // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
760     // more text than you insert.  In that case all of the text that was around originally should be selected.
761     if (m_openedByBackwardDelete)
762         setStartingSelection(selectionAfterUndo);
763     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete, /* mergeBlocksAfterDelete*/ true, /* replace*/ false, expandForSpecialElements, /*sanitizeMarkup*/ true);
764     setSmartDelete(false);
765     typingAddedToOpenCommand(DeleteKey);
766 }
767
768 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing)
769 {
770     Frame& frame = this->frame();
771     Ref<Frame> protector(frame);
772
773     frame.editor().updateMarkersForWordsAffectedByEditing(false);
774
775     VisibleSelection selectionToDelete;
776     VisibleSelection selectionAfterUndo;
777     bool expandForSpecialElements = !endingSelection().isCaret();
778
779     switch (endingSelection().selectionType()) {
780     case VisibleSelection::RangeSelection:
781         selectionToDelete = endingSelection();
782         selectionAfterUndo = selectionToDelete;
783         break;
784     case VisibleSelection::CaretSelection: {
785         m_smartDelete = false;
786
787         // Handle delete at beginning-of-block case.
788         // Do nothing in the case that the caret is at the start of a
789         // root editable element or at the start of a document.
790         FrameSelection selection;
791         selection.setSelection(endingSelection());
792         selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
793         if (shouldAddToKillRing && selection.isCaret() && granularity != CharacterGranularity)
794             selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
795
796         Position downstreamEnd = endingSelection().end().downstream();
797         VisiblePosition visibleEnd = endingSelection().visibleEnd();
798         Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
799         if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell))
800             return;
801         if (visibleEnd == endOfParagraph(visibleEnd))
802             downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
803         // When deleting tables: Select the table first, then perform the deletion
804         if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable()
805             && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(*downstreamEnd.containerNode())) {
806             setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
807             typingAddedToOpenCommand(ForwardDeleteKey);
808             return;
809         }
810
811         // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
812         if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
813             selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
814
815         selectionToDelete = selection.selection();
816         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
817             selectionAfterUndo = selectionToDelete;
818         else {
819             // It's a little tricky to compute what the starting selection would have been in the original document.
820             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
821             // the current state of the document and we'll get the wrong result.
822             Position extent = startingSelection().end();
823             if (extent.containerNode() != selectionToDelete.end().containerNode())
824                 extent = selectionToDelete.extent();
825             else {
826                 int extraCharacters;
827                 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
828                     extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
829                 else
830                     extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
831                 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
832             }
833             selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
834         }
835         break;
836     }
837     case VisibleSelection::NoSelection:
838         ASSERT_NOT_REACHED();
839         break;
840     }
841     
842     ASSERT(!selectionToDelete.isNone());
843     if (selectionToDelete.isNone()) {
844 #if PLATFORM(IOS_FAMILY)
845         // Workaround for this bug:
846         // <rdar://problem/4653755> UIKit text widgets should use WebKit editing API to manipulate text
847         setEndingSelection(frame.selection().selection());
848         closeTyping(&frame);
849 #endif
850         return;
851     }
852     
853     if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete))
854         return;
855
856     if (!willAddTypingToOpenCommand(ForwardDeleteKey, granularity, { }, selectionToDelete.firstRange()))
857         return;
858
859     // Post the accessibility notification before actually deleting the content while selectionToDelete is still valid
860     postTextStateChangeNotificationForDeletion(selectionToDelete);
861
862     if (shouldAddToKillRing)
863         frame.editor().addRangeToKillRing(*selectionToDelete.toNormalizedRange().get(), Editor::KillRingInsertionMode::AppendText);
864     // make undo select what was deleted
865     setStartingSelection(selectionAfterUndo);
866     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete, /* mergeBlocksAfterDelete*/ true, /* replace*/ false, expandForSpecialElements, /*sanitizeMarkup*/ true);
867     setSmartDelete(false);
868     typingAddedToOpenCommand(ForwardDeleteKey);
869 }
870
871 void TypingCommand::deleteSelection(bool smartDelete)
872 {
873     if (!willAddTypingToOpenCommand(DeleteSelection, CharacterGranularity))
874         return;
875
876     CompositeEditCommand::deleteSelection(smartDelete);
877     typingAddedToOpenCommand(DeleteSelection);
878 }
879
880 #if PLATFORM(IOS_FAMILY)
881 class FriendlyEditCommand : public EditCommand {
882 public:
883     void setEndingSelection(const VisibleSelection& selection)
884     {
885         EditCommand::setEndingSelection(selection);
886     }
887 };
888
889 void TypingCommand::setEndingSelectionOnLastInsertCommand(const VisibleSelection& selection)
890 {
891     if (!m_commands.isEmpty()) {
892         EditCommand* lastCommand = m_commands.last().get();
893         if (lastCommand->isInsertTextCommand())
894             static_cast<FriendlyEditCommand*>(lastCommand)->setEndingSelection(selection);
895     }
896 }
897 #endif
898
899 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
900 {
901     switch (commandType) {
902     case DeleteSelection:
903     case DeleteKey:
904     case ForwardDeleteKey:
905     case InsertParagraphSeparator:
906     case InsertLineBreak:
907         m_preservesTypingStyle = true;
908         return;
909     case InsertParagraphSeparatorInQuotedContent:
910     case InsertText:
911         m_preservesTypingStyle = false;
912         return;
913     }
914     ASSERT_NOT_REACHED();
915     m_preservesTypingStyle = false;
916 }
917
918 bool TypingCommand::isTypingCommand() const
919 {
920     return true;
921 }
922
923 } // namespace WebCore