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