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