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