Home | History | Annotate | Download | only in editing
      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 
     45 namespace WebCore {
     46 
     47 using namespace HTMLNames;
     48 
     49 class TypingCommandLineOperation
     50 {
     51 public:
     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 
     70 private:
     71     TypingCommand* m_typingCommand;
     72     bool m_selectInsertedText;
     73     const String& m_text;
     74 };
     75 
     76 TypingCommand::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 
     93 void 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 
    110 void 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 
    129 void 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 
    145 void 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 
    156 void 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.
    168 void 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 
    197 void 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 
    208 void 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 
    218 void 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 
    229 PassRefPtr<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 
    240 void TypingCommand::closeTyping(Frame* frame)
    241 {
    242     if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame))
    243         lastTypingCommand->closeTyping();
    244 }
    245 
    246 void 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 
    282 EditAction TypingCommand::editingAction() const
    283 {
    284     return EditActionTyping;
    285 }
    286 
    287 void 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 
    312 void 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 
    326 void 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 
    337 void 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 
    347 void TypingCommand::insertLineBreak()
    348 {
    349     if (!canAppendNewLineFeedToSelection(endingSelection()))
    350         return;
    351 
    352     applyCommandToComposite(InsertLineBreakCommand::create(document()));
    353     typingAddedToOpenCommand(InsertLineBreak);
    354 }
    355 
    356 void TypingCommand::insertParagraphSeparator()
    357 {
    358     if (!canAppendNewLineFeedToSelection(endingSelection()))
    359         return;
    360 
    361     applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
    362     typingAddedToOpenCommand(InsertParagraphSeparator);
    363 }
    364 
    365 void 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 
    378 bool 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 
    399 void 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 
    503 void 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 
    593 void TypingCommand::deleteSelection(bool smartDelete)
    594 {
    595     CompositeEditCommand::deleteSelection(smartDelete);
    596     typingAddedToOpenCommand(DeleteSelection);
    597 }
    598 
    599 void 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 
    618 bool TypingCommand::isTypingCommand() const
    619 {
    620     return true;
    621 }
    622 
    623 } // namespace WebCore
    624