ff8f809bff4c30d35535db43679373484c0f3124
[WebKit-https.git] / Source / WebCore / editing / TypingCommand.cpp
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008 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 "BreakBlockquoteCommand.h"
30 #include "DeleteSelectionCommand.h"
31 #include "Document.h"
32 #include "Editor.h"
33 #include "Element.h"
34 #include "Frame.h"
35 #include "HTMLNames.h"
36 #include "InsertLineBreakCommand.h"
37 #include "InsertParagraphSeparatorCommand.h"
38 #include "InsertTextCommand.h"
39 #include "RenderElement.h"
40 #include "TextIterator.h"
41 #include "VisibleUnits.h"
42 #include "htmlediting.h"
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 class TypingCommandLineOperation
49 {
50 public:
51     TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text)
52     : m_typingCommand(typingCommand)
53     , m_selectInsertedText(selectInsertedText)
54     , m_text(text)
55     { }
56     
57     void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const
58     {
59         if (isLastLine) {
60             if (!lineOffset || lineLength > 0)
61                 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText);
62         } else {
63             if (lineLength > 0)
64                 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false);
65             m_typingCommand->insertParagraphSeparator();
66         }
67     }
68     
69 private:
70     TypingCommand* m_typingCommand;
71     bool m_selectInsertedText;
72     const String& m_text;
73 };
74
75 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
76     : TextInsertionBaseCommand(document)
77     , m_commandType(commandType)
78     , m_textToInsert(textToInsert)
79     , m_openForMoreTyping(true)
80     , m_selectInsertedText(options & SelectInsertedText)
81     , m_smartDelete(options & SmartDelete)
82     , m_granularity(granularity)
83     , m_compositionType(compositionType)
84     , m_killRing(options & KillRing)
85     , m_openedByBackwardDelete(false)
86     , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
87     , m_shouldPreventSpellChecking(options & PreventSpellChecking)
88 {
89     updatePreservesTypingStyle(m_commandType);
90 }
91
92 void TypingCommand::deleteSelection(Document& document, Options options)
93 {
94     Frame* frame = document.frame();
95     ASSERT(frame);
96
97     if (!frame->selection().isRange())
98         return;
99
100     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
101         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
102         lastTypingCommand->deleteSelection(options & SmartDelete);
103         return;
104     }
105
106     TypingCommand::create(document, DeleteSelection, "", options)->apply();
107 }
108
109 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity)
110 {
111     if (granularity == CharacterGranularity) {
112         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
113             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document.frame());
114             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
115             lastTypingCommand->deleteKeyPressed(granularity, options & KillRing);
116             return;
117         }
118     }
119
120     TypingCommand::create(document, DeleteKey, "", options, granularity)->apply();
121 }
122
123 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity)
124 {
125     // FIXME: Forward delete in TextEdit appears to open and close a new typing command.
126     Frame* frame = document.frame();
127     if (granularity == CharacterGranularity) {
128         if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
129             updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
130             lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
131             lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing);
132             return;
133         }
134     }
135
136     TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply();
137 }
138
139 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame)
140 {
141     ASSERT(frame);
142     VisibleSelection currentSelection = frame->selection().selection();
143     if (currentSelection == typingCommand->endingSelection())
144         return;
145
146     typingCommand->setStartingSelection(currentSelection);
147     typingCommand->setEndingSelection(currentSelection);
148 }
149
150 void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition)
151 {
152     Frame* frame = document.frame();
153     ASSERT(frame);
154
155     if (!text.isEmpty())
156         frame->editor().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text.deprecatedCharacters()[0]));
157     
158     insertText(document, text, frame->selection().selection(), options, composition);
159 }
160
161 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection.
162 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
163 {
164     RefPtr<Frame> frame = document.frame();
165     ASSERT(frame);
166
167     VisibleSelection currentSelection = frame->selection().selection();
168
169     String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate);
170     
171     // Set the starting and ending selection appropriately if we are using a selection
172     // that is different from the current selection.  In the future, we should change EditCommand
173     // to deal with custom selections in a general way that can be used by all of the commands.
174     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) {
175         if (lastTypingCommand->endingSelection() != selectionForInsertion) {
176             lastTypingCommand->setStartingSelection(selectionForInsertion);
177             lastTypingCommand->setEndingSelection(selectionForInsertion);
178         }
179
180         lastTypingCommand->setCompositionType(compositionType);
181         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
182         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
183         lastTypingCommand->insertText(newText, options & SelectInsertedText);
184         return;
185     }
186
187     RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType);
188     applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection);
189 }
190
191 void TypingCommand::insertLineBreak(Document& document, Options options)
192 {
193     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
194         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
195         lastTypingCommand->insertLineBreak();
196         return;
197     }
198
199     applyCommand(TypingCommand::create(document, InsertLineBreak, "", options));
200 }
201
202 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
203 {
204     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
205         lastTypingCommand->insertParagraphSeparatorInQuotedContent();
206         return;
207     }
208
209     applyCommand(TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent));
210 }
211
212 void TypingCommand::insertParagraphSeparator(Document& document, Options options)
213 {
214     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
215         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
216         lastTypingCommand->insertParagraphSeparator();
217         return;
218     }
219
220     applyCommand(TypingCommand::create(document, InsertParagraphSeparator, "", options));
221 }
222
223 PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame* frame)
224 {
225     ASSERT(frame);
226
227     RefPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand();
228     if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping())
229         return 0;
230
231     return static_cast<TypingCommand*>(lastEditCommand.get());
232 }
233
234 void TypingCommand::closeTyping(Frame* frame)
235 {
236     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame))
237         lastTypingCommand->closeTyping();
238 }
239
240 #if PLATFORM(IOS)
241 void TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(Frame* frame, const VisibleSelection& newSelection)
242 {
243     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
244         lastTypingCommand->setEndingSelection(newSelection);
245         lastTypingCommand->setEndingSelectionOnLastInsertCommand(newSelection);
246     }
247 }
248 #endif
249
250 void TypingCommand::doApply()
251 {
252     if (!endingSelection().isNonOrphanedCaretOrRange())
253         return;
254         
255     if (m_commandType == DeleteKey)
256         if (m_commands.isEmpty())
257             m_openedByBackwardDelete = true;
258
259     switch (m_commandType) {
260     case DeleteSelection:
261         deleteSelection(m_smartDelete);
262         return;
263     case DeleteKey:
264         deleteKeyPressed(m_granularity, m_killRing);
265         return;
266     case ForwardDeleteKey:
267         forwardDeleteKeyPressed(m_granularity, m_killRing);
268         return;
269     case InsertLineBreak:
270         insertLineBreak();
271         return;
272     case InsertParagraphSeparator:
273         insertParagraphSeparator();
274         return;
275     case InsertParagraphSeparatorInQuotedContent:
276         insertParagraphSeparatorInQuotedContent();
277         return;
278     case InsertText:
279         insertText(m_textToInsert, m_selectInsertedText);
280         return;
281     }
282
283     ASSERT_NOT_REACHED();
284 }
285
286 EditAction TypingCommand::editingAction() const
287 {
288     return EditActionTyping;
289 }
290
291 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
292 {
293     Frame& frame = this->frame();
294
295 #if PLATFORM(MAC)
296     if (!frame.editor().isContinuousSpellCheckingEnabled()
297         && !frame.editor().isAutomaticQuoteSubstitutionEnabled()
298         && !frame.editor().isAutomaticLinkDetectionEnabled()
299         && !frame.editor().isAutomaticDashSubstitutionEnabled()
300         && !frame.editor().isAutomaticTextReplacementEnabled())
301             return;
302 #else
303     if (!frame.editor().isContinuousSpellCheckingEnabled())
304         return;
305 #endif
306     // Take a look at the selection that results after typing and determine whether we need to spellcheck. 
307     // Since the word containing the current selection is never marked, this does a check to
308     // see if typing made a new word that is not in the current selection. Basically, you
309     // get this by being at the end of a word and typing a space.    
310     VisiblePosition start(endingSelection().start(), endingSelection().affinity());
311     VisiblePosition previous = start.previous();
312     if (previous.isNotNull()) {
313 #if !PLATFORM(IOS)
314         VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
315         VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
316         if (p1 != p2) {
317             RefPtr<Range> range = makeRange(p1, p2);
318             String strippedPreviousWord;
319             if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent))
320                 strippedPreviousWord = plainText(range.get()).stripWhiteSpace();
321             frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty());
322         } else if (commandType == TypingCommand::InsertText)
323             frame.editor().startAlternativeTextUITimer();
324 #else
325         UNUSED_PARAM(commandType);
326         // If this bug gets fixed, this PLATFORM(IOS) code could be removed:
327         // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop
328         EWordSide startWordSide = LeftWordIfOnBoundary;
329         UChar32 c = previous.characterAfter();
330         // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same
331         // way as TextIterator, so we do an isEndOfParagraph check here.
332         if (isSpaceOrNewline(c) || c == 0xA0 || isEndOfParagraph(previous)) {
333             startWordSide = RightWordIfOnBoundary;
334         }
335         VisiblePosition p1 = startOfWord(previous, startWordSide);
336         VisiblePosition p2 = startOfWord(start, startWordSide);
337         if (p1 != p2)
338             frame.editor().markMisspellingsAfterTypingToWord(p1, endingSelection(), false);
339 #endif // !PLATFORM(IOS)
340     }
341 }
342
343 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
344 {
345     Frame& frame = this->frame();
346
347     updatePreservesTypingStyle(commandTypeForAddedTyping);
348
349 #if PLATFORM(COCOA)
350     frame.editor().appliedEditing(this);
351     // Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes.
352     if (!m_shouldPreventSpellChecking)
353         markMisspellingsAfterTyping(commandTypeForAddedTyping);
354 #else
355     // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
356     markMisspellingsAfterTyping(commandTypeForAddedTyping);
357     frame.editor().appliedEditing(this);
358 #endif
359 }
360
361 void TypingCommand::insertText(const String &text, bool selectInsertedText)
362 {
363     // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
364     // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
365     // an existing selection; at the moment they can either put the caret after what's inserted or
366     // select what's inserted, but there's no way to "extend selection" to include both an old selection
367     // that ends just before where we want to insert text and the newly inserted text.
368     TypingCommandLineOperation operation(this, selectInsertedText, text);
369     forEachLineInString(text, operation);
370 }
371
372 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
373 {
374     RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText,
375         m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces);
376
377     applyCommandToComposite(command, endingSelection());
378
379     typingAddedToOpenCommand(InsertText);
380 }
381
382 void TypingCommand::insertLineBreak()
383 {
384     if (!canAppendNewLineFeedToSelection(endingSelection()))
385         return;
386
387     applyCommandToComposite(InsertLineBreakCommand::create(document()));
388     typingAddedToOpenCommand(InsertLineBreak);
389 }
390
391 void TypingCommand::insertParagraphSeparator()
392 {
393     if (!canAppendNewLineFeedToSelection(endingSelection()))
394         return;
395
396     applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
397     typingAddedToOpenCommand(InsertParagraphSeparator);
398 }
399
400 void TypingCommand::insertParagraphSeparatorInQuotedContent()
401 {
402     // If the selection starts inside a table, just insert the paragraph separator normally
403     // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
404     if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
405         insertParagraphSeparator();
406         return;
407     }
408         
409     applyCommandToComposite(BreakBlockquoteCommand::create(document()));
410     typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
411 }
412
413 bool TypingCommand::makeEditableRootEmpty()
414 {
415     Element* root = endingSelection().rootEditableElement();
416     if (!root || !root->firstChild())
417         return false;
418
419     if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) {
420         // If there is a single child and it could be a placeholder, leave it alone.
421         if (root->renderer() && root->renderer()->isRenderBlockFlow())
422             return false;
423     }
424
425     while (Node* child = root->firstChild())
426         removeNode(child);
427
428     addBlockPlaceholderIfNeeded(root);
429     setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional()));
430
431     return true;
432 }
433
434 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
435 {
436     Frame& frame = this->frame();
437
438     frame.editor().updateMarkersForWordsAffectedByEditing(false);
439
440     VisibleSelection selectionToDelete;
441     VisibleSelection selectionAfterUndo;
442
443     switch (endingSelection().selectionType()) {
444     case VisibleSelection::RangeSelection:
445         selectionToDelete = endingSelection();
446         selectionAfterUndo = selectionToDelete;
447         break;
448     case VisibleSelection::CaretSelection: {
449         // After breaking out of an empty mail blockquote, we still want continue with the deletion
450         // so actual content will get deleted, and not just the quote style.
451         if (breakOutOfEmptyMailBlockquotedParagraph())
452             typingAddedToOpenCommand(DeleteKey);
453
454         m_smartDelete = false;
455
456         FrameSelection selection;
457         selection.setSelection(endingSelection());
458         selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
459         if (killRing && selection.isCaret() && granularity != CharacterGranularity)
460             selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
461
462         if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) {
463             // When the caret is at the start of the editable area in an empty list item, break out of the list item.
464             if (breakOutOfEmptyListItem()) {
465                 typingAddedToOpenCommand(DeleteKey);
466                 return;
467             }
468             // When there are no visible positions in the editing root, delete its entire contents.
469             if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
470                 typingAddedToOpenCommand(DeleteKey);
471                 return;
472             }
473         }
474
475         VisiblePosition visibleStart(endingSelection().visibleStart());
476         // If we have a caret selection at the beginning of a cell, we have nothing to do.
477         Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell);
478         if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell))
479             return;
480
481         // If the caret is at the start of a paragraph after a table, move content into the last table cell.
482         if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
483             // Unless the caret is just before a table.  We don't want to move a table into the last table cell.
484             if (isLastPositionBeforeTable(visibleStart))
485                 return;
486             // Extend the selection backward into the last cell, then deletion will handle the move.
487             selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
488         // If the caret is just after a table, select the table and don't delete anything.
489         } else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
490             setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional()));
491             typingAddedToOpenCommand(DeleteKey);
492             return;
493         }
494
495         selectionToDelete = selection.selection();
496
497         if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode()
498             && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) {
499             // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
500             selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
501         }
502
503         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
504             selectionAfterUndo = selectionToDelete;
505         else
506             // It's a little tricky to compute what the starting selection would have been in the original document.
507             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
508             // the current state of the document and we'll get the wrong result.
509             selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
510         break;
511     }
512     case VisibleSelection::NoSelection:
513         ASSERT_NOT_REACHED();
514         break;
515     }
516     
517     ASSERT(!selectionToDelete.isNone());
518     if (selectionToDelete.isNone()) {
519 #if PLATFORM(IOS)
520         // Workaround for this bug:
521         // <rdar://problem/4653755> UIKit text widgets should use WebKit editing API to manipulate text
522         setEndingSelection(frame.selection().selection());
523         closeTyping(&frame);
524 #endif
525         return;
526     }
527     
528     if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete))
529         return;
530     
531     if (killRing)
532         frame.editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
533     // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
534     // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
535     // more text than you insert.  In that case all of the text that was around originally should be selected.
536     if (m_openedByBackwardDelete)
537         setStartingSelection(selectionAfterUndo);
538     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
539     setSmartDelete(false);
540     typingAddedToOpenCommand(DeleteKey);
541 }
542
543 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
544 {
545     Frame& frame = this->frame();
546
547     frame.editor().updateMarkersForWordsAffectedByEditing(false);
548
549     VisibleSelection selectionToDelete;
550     VisibleSelection selectionAfterUndo;
551
552     switch (endingSelection().selectionType()) {
553     case VisibleSelection::RangeSelection:
554         selectionToDelete = endingSelection();
555         selectionAfterUndo = selectionToDelete;
556         break;
557     case VisibleSelection::CaretSelection: {
558         m_smartDelete = false;
559
560         // Handle delete at beginning-of-block case.
561         // Do nothing in the case that the caret is at the start of a
562         // root editable element or at the start of a document.
563         FrameSelection selection;
564         selection.setSelection(endingSelection());
565         selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
566         if (killRing && selection.isCaret() && granularity != CharacterGranularity)
567             selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
568
569         Position downstreamEnd = endingSelection().end().downstream();
570         VisiblePosition visibleEnd = endingSelection().visibleEnd();
571         Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
572         if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell))
573             return;
574         if (visibleEnd == endOfParagraph(visibleEnd))
575             downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
576         // When deleting tables: Select the table first, then perform the deletion
577         if (downstreamEnd.containerNode() && downstreamEnd.containerNode()->renderer() && downstreamEnd.containerNode()->renderer()->isTable()
578             && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
579             setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
580             typingAddedToOpenCommand(ForwardDeleteKey);
581             return;
582         }
583
584         // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
585         if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
586             selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
587
588         selectionToDelete = selection.selection();
589         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
590             selectionAfterUndo = selectionToDelete;
591         else {
592             // It's a little tricky to compute what the starting selection would have been in the original document.
593             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
594             // the current state of the document and we'll get the wrong result.
595             Position extent = startingSelection().end();
596             if (extent.containerNode() != selectionToDelete.end().containerNode())
597                 extent = selectionToDelete.extent();
598             else {
599                 int extraCharacters;
600                 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
601                     extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
602                 else
603                     extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
604                 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
605             }
606             selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
607         }
608         break;
609     }
610     case VisibleSelection::NoSelection:
611         ASSERT_NOT_REACHED();
612         break;
613     }
614     
615     ASSERT(!selectionToDelete.isNone());
616     if (selectionToDelete.isNone()) {
617 #if PLATFORM(IOS)
618         // Workaround for this bug:
619         // <rdar://problem/4653755> UIKit text widgets should use WebKit editing API to manipulate text
620         setEndingSelection(frame.selection().selection());
621         closeTyping(&frame);
622 #endif
623         return;
624     }
625     
626     if (selectionToDelete.isCaret() || !frame.selection().shouldDeleteSelection(selectionToDelete))
627         return;
628         
629     if (killRing)
630         frame.editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
631     // make undo select what was deleted
632     setStartingSelection(selectionAfterUndo);
633     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
634     setSmartDelete(false);
635     typingAddedToOpenCommand(ForwardDeleteKey);
636 }
637
638 void TypingCommand::deleteSelection(bool smartDelete)
639 {
640     CompositeEditCommand::deleteSelection(smartDelete);
641     typingAddedToOpenCommand(DeleteSelection);
642 }
643
644 #if PLATFORM(IOS)
645 class FriendlyEditCommand : public EditCommand {
646 public:
647     void setEndingSelection(const VisibleSelection& selection)
648     {
649         EditCommand::setEndingSelection(selection);
650     }
651 };
652
653 void TypingCommand::setEndingSelectionOnLastInsertCommand(const VisibleSelection& selection)
654 {
655     if (!m_commands.isEmpty()) {
656         EditCommand* lastCommand = m_commands.last().get();
657         if (lastCommand->isInsertTextCommand())
658             static_cast<FriendlyEditCommand*>(lastCommand)->setEndingSelection(selection);
659     }
660 }
661 #endif
662
663 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
664 {
665     switch (commandType) {
666     case DeleteSelection:
667     case DeleteKey:
668     case ForwardDeleteKey:
669     case InsertParagraphSeparator:
670     case InsertLineBreak:
671         m_preservesTypingStyle = true;
672         return;
673     case InsertParagraphSeparatorInQuotedContent:
674     case InsertText:
675         m_preservesTypingStyle = false;
676         return;
677     }
678     ASSERT_NOT_REACHED();
679     m_preservesTypingStyle = false;
680 }
681
682 bool TypingCommand::isTypingCommand() const
683 {
684     return true;
685 }
686
687 } // namespace WebCore