Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2005 Apple Computer, 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/DeleteSelectionCommand.h"
     28 
     29 #include "core/HTMLNames.h"
     30 #include "core/dom/Document.h"
     31 #include "core/dom/Element.h"
     32 #include "core/dom/NodeTraversal.h"
     33 #include "core/dom/Text.h"
     34 #include "core/editing/EditingBoundary.h"
     35 #include "core/editing/Editor.h"
     36 #include "core/editing/VisibleUnits.h"
     37 #include "core/editing/htmlediting.h"
     38 #include "core/frame/LocalFrame.h"
     39 #include "core/html/HTMLInputElement.h"
     40 #include "core/rendering/RenderTableCell.h"
     41 
     42 namespace WebCore {
     43 
     44 using namespace HTMLNames;
     45 
     46 static bool isTableCellEmpty(Node* cell)
     47 {
     48     ASSERT(isTableCell(cell));
     49     return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPositionInNode(cell));
     50 }
     51 
     52 static bool isTableRowEmpty(Node* row)
     53 {
     54     if (!isHTMLTableRowElement(row))
     55         return false;
     56 
     57     for (Node* child = row->firstChild(); child; child = child->nextSibling())
     58         if (isTableCell(child) && !isTableCellEmpty(child))
     59             return false;
     60 
     61     return true;
     62 }
     63 
     64 DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool expandForSpecialElements, bool sanitizeMarkup)
     65     : CompositeEditCommand(document)
     66     , m_hasSelectionToDelete(false)
     67     , m_smartDelete(smartDelete)
     68     , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
     69     , m_needPlaceholder(false)
     70     , m_expandForSpecialElements(expandForSpecialElements)
     71     , m_pruneStartBlockIfNecessary(false)
     72     , m_startsAtEmptyLine(false)
     73     , m_sanitizeMarkup(sanitizeMarkup)
     74     , m_startBlock(nullptr)
     75     , m_endBlock(nullptr)
     76     , m_typingStyle(nullptr)
     77     , m_deleteIntoBlockquoteStyle(nullptr)
     78 {
     79 }
     80 
     81 DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool expandForSpecialElements, bool sanitizeMarkup)
     82     : CompositeEditCommand(*selection.start().document())
     83     , m_hasSelectionToDelete(true)
     84     , m_smartDelete(smartDelete)
     85     , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
     86     , m_needPlaceholder(false)
     87     , m_expandForSpecialElements(expandForSpecialElements)
     88     , m_pruneStartBlockIfNecessary(false)
     89     , m_startsAtEmptyLine(false)
     90     , m_sanitizeMarkup(sanitizeMarkup)
     91     , m_selectionToDelete(selection)
     92     , m_startBlock(nullptr)
     93     , m_endBlock(nullptr)
     94     , m_typingStyle(nullptr)
     95     , m_deleteIntoBlockquoteStyle(nullptr)
     96 {
     97 }
     98 
     99 void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end)
    100 {
    101     Node* startSpecialContainer = 0;
    102     Node* endSpecialContainer = 0;
    103 
    104     start = m_selectionToDelete.start();
    105     end = m_selectionToDelete.end();
    106 
    107     // For HRs, we'll get a position at (HR,1) when hitting delete from the beginning of the previous line, or (HR,0) when forward deleting,
    108     // but in these cases, we want to delete it, so manually expand the selection
    109     if (isHTMLHRElement(*start.deprecatedNode()))
    110         start = positionBeforeNode(start.deprecatedNode());
    111     else if (isHTMLHRElement(*end.deprecatedNode()))
    112         end = positionAfterNode(end.deprecatedNode());
    113 
    114     // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expansion.
    115     if (!m_expandForSpecialElements)
    116         return;
    117 
    118     while (1) {
    119         startSpecialContainer = 0;
    120         endSpecialContainer = 0;
    121 
    122         Position s = positionBeforeContainingSpecialElement(start, &startSpecialContainer);
    123         Position e = positionAfterContainingSpecialElement(end, &endSpecialContainer);
    124 
    125         if (!startSpecialContainer && !endSpecialContainer)
    126             break;
    127 
    128         if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || VisiblePosition(end) != m_selectionToDelete.visibleEnd())
    129             break;
    130 
    131         // If we're going to expand to include the startSpecialContainer, it must be fully selected.
    132         if (startSpecialContainer && !endSpecialContainer && comparePositions(positionInParentAfterNode(*startSpecialContainer), end) > -1)
    133             break;
    134 
    135         // If we're going to expand to include the endSpecialContainer, it must be fully selected.
    136         if (endSpecialContainer && !startSpecialContainer && comparePositions(start, positionInParentBeforeNode(*endSpecialContainer)) > -1)
    137             break;
    138 
    139         if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer))
    140             // Don't adjust the end yet, it is the end of a special element that contains the start
    141             // special element (which may or may not be fully selected).
    142             start = s;
    143         else if (endSpecialContainer && endSpecialContainer->isDescendantOf(startSpecialContainer))
    144             // Don't adjust the start yet, it is the start of a special element that contains the end
    145             // special element (which may or may not be fully selected).
    146             end = e;
    147         else {
    148             start = s;
    149             end = e;
    150         }
    151     }
    152 }
    153 
    154 void DeleteSelectionCommand::setStartingSelectionOnSmartDelete(const Position& start, const Position& end)
    155 {
    156     bool isBaseFirst = startingSelection().isBaseFirst();
    157     VisiblePosition newBase(isBaseFirst ? start : end);
    158     VisiblePosition newExtent(isBaseFirst ? end : start);
    159     setStartingSelection(VisibleSelection(newBase, newExtent, startingSelection().isDirectional()));
    160 }
    161 
    162 void DeleteSelectionCommand::initializePositionData()
    163 {
    164     Position start, end;
    165     initializeStartEnd(start, end);
    166 
    167     ASSERT(isEditablePosition(start, ContentIsEditable, DoNotUpdateStyle));
    168     if (!isEditablePosition(end, ContentIsEditable, DoNotUpdateStyle))
    169         end = lastEditablePositionBeforePositionInRoot(end, highestEditableRoot(start));
    170 
    171     m_upstreamStart = start.upstream();
    172     m_downstreamStart = start.downstream();
    173     m_upstreamEnd = end.upstream();
    174     m_downstreamEnd = end.downstream();
    175 
    176     m_startRoot = editableRootForPosition(start);
    177     m_endRoot = editableRootForPosition(end);
    178 
    179     m_startTableRow = enclosingNodeOfType(start, &isHTMLTableRowElement);
    180     m_endTableRow = enclosingNodeOfType(end, &isHTMLTableRowElement);
    181 
    182     // Don't move content out of a table cell.
    183     // If the cell is non-editable, enclosingNodeOfType won't return it by default, so
    184     // tell that function that we don't care if it returns non-editable nodes.
    185     Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary);
    186     Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary);
    187     // FIXME: This isn't right.  A borderless table with two rows and a single column would appear as two paragraphs.
    188     if (endCell && endCell != startCell)
    189         m_mergeBlocksAfterDelete = false;
    190 
    191     // Usually the start and the end of the selection to delete are pulled together as a result of the deletion.
    192     // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret
    193     // and receive the placeholder after deletion.
    194     VisiblePosition visibleEnd(m_downstreamEnd);
    195     if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd))
    196         m_endingPosition = m_downstreamEnd;
    197     else
    198         m_endingPosition = m_downstreamStart;
    199 
    200     // We don't want to merge into a block if it will mean changing the quote level of content after deleting
    201     // selections that contain a whole number paragraphs plus a line break, since it is unclear to most users
    202     // that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior
    203     // for indented paragraphs.
    204     // Only apply this rule if the endingSelection is a range selection.  If it is a caret, then other operations have created
    205     // the selection we're deleting (like the process of creating a selection to delete during a backspace), and the user isn't in the situation described above.
    206     if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end)
    207             && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start))
    208             && endingSelection().isRange()) {
    209         m_mergeBlocksAfterDelete = false;
    210         m_pruneStartBlockIfNecessary = true;
    211     }
    212 
    213     // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
    214     m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity());
    215     m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
    216 
    217     if (m_smartDelete) {
    218 
    219         // skip smart delete if the selection to delete already starts or ends with whitespace
    220         Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent();
    221         bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
    222         if (!skipSmartDelete)
    223             skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
    224 
    225         // extend selection upstream if there is whitespace there
    226         bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull();
    227         if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
    228             VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous();
    229             pos = visiblePos.deepEquivalent();
    230             // Expand out one character upstream for smart delete and recalculate
    231             // positions based on this change.
    232             m_upstreamStart = pos.upstream();
    233             m_downstreamStart = pos.downstream();
    234             m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
    235 
    236             setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd);
    237         }
    238 
    239         // trailing whitespace is only considered for smart delete if there is no leading
    240         // whitespace, as in the case where you double-click the first word of a paragraph.
    241         if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
    242             // Expand out one character downstream for smart delete and recalculate
    243             // positions based on this change.
    244             pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent();
    245             m_upstreamEnd = pos.upstream();
    246             m_downstreamEnd = pos.downstream();
    247             m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
    248 
    249             setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd);
    250         }
    251     }
    252 
    253     // We must pass call parentAnchoredEquivalent on the positions since some editing positions
    254     // that appear inside their nodes aren't really inside them.  [hr, 0] is one example.
    255     // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing element getters
    256     // like the one below, since editing functions should obviously accept editing positions.
    257     // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable
    258     // node.  This was done to match existing behavior, but it seems wrong.
    259     m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
    260     m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
    261 }
    262 
    263 // We don't want to inherit style from an element which can't have contents.
    264 static bool shouldNotInheritStyleFrom(const Node& node)
    265 {
    266     return !node.canContainRangeEndPoint();
    267 }
    268 
    269 void DeleteSelectionCommand::saveTypingStyleState()
    270 {
    271     // A common case is deleting characters that are all from the same text node. In
    272     // that case, the style at the start of the selection before deletion will be the
    273     // same as the style at the start of the selection after deletion (since those
    274     // two positions will be identical). Therefore there is no need to save the
    275     // typing style at the start of the selection, nor is there a reason to
    276     // compute the style at the start of the selection after deletion (see the
    277     // early return in calculateTypingStyleAfterDelete).
    278     if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() && m_upstreamStart.deprecatedNode()->isTextNode())
    279         return;
    280 
    281     if (shouldNotInheritStyleFrom(*m_selectionToDelete.start().anchorNode()))
    282         return;
    283 
    284     // Figure out the typing style in effect before the delete is done.
    285     m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), EditingStyle::EditingPropertiesInEffect);
    286     m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDelete.start()));
    287 
    288     // If we're deleting into a Mail blockquote, save the style at end() instead of start()
    289     // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
    290     if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote))
    291         m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end());
    292     else
    293         m_deleteIntoBlockquoteStyle = nullptr;
    294 }
    295 
    296 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
    297 {
    298     Node* nodeAfterUpstreamStart = m_upstreamStart.computeNodeAfterPosition();
    299     Node* nodeAfterDownstreamStart = m_downstreamStart.computeNodeAfterPosition();
    300     // Upstream end will appear before BR due to canonicalization
    301     Node* nodeAfterUpstreamEnd = m_upstreamEnd.computeNodeAfterPosition();
    302 
    303     if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart)
    304         return false;
    305 
    306     // Check for special-case where the selection contains only a BR on a line by itself after another BR.
    307     bool upstreamStartIsBR = isHTMLBRElement(*nodeAfterUpstreamStart);
    308     bool downstreamStartIsBR = isHTMLBRElement(*nodeAfterDownstreamStart);
    309     bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && nodeAfterDownstreamStart == nodeAfterUpstreamEnd;
    310     if (isBROnLineByItself) {
    311         removeNode(nodeAfterDownstreamStart);
    312         return true;
    313     }
    314 
    315     // FIXME: This code doesn't belong in here.
    316     // We detect the case where the start is an empty line consisting of BR not wrapped in a block element.
    317     if (upstreamStartIsBR && downstreamStartIsBR && !(isStartOfBlock(VisiblePosition(positionBeforeNode(nodeAfterUpstreamStart))) && isEndOfBlock(VisiblePosition(positionAfterNode(nodeAfterUpstreamStart))))) {
    318         m_startsAtEmptyLine = true;
    319         m_endingPosition = m_downstreamEnd;
    320     }
    321 
    322     return false;
    323 }
    324 
    325 static Position firstEditablePositionInNode(Node* node)
    326 {
    327     ASSERT(node);
    328     Node* next = node;
    329     while (next && !next->rendererIsEditable())
    330         next = NodeTraversal::next(*next, node);
    331     return next ? firstPositionInOrBeforeNode(next) : Position();
    332 }
    333 
    334 void DeleteSelectionCommand::removeNode(PassRefPtrWillBeRawPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
    335 {
    336     if (!node)
    337         return;
    338 
    339     if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.get()) && node->isDescendantOf(m_endRoot.get()))) {
    340         // If a node is not in both the start and end editable roots, remove it only if its inside an editable region.
    341         if (!node->parentNode()->rendererIsEditable()) {
    342             // Don't remove non-editable atomic nodes.
    343             if (!node->firstChild())
    344                 return;
    345             // Search this non-editable region for editable regions to empty.
    346             RefPtrWillBeRawPtr<Node> child = node->firstChild();
    347             while (child) {
    348                 RefPtrWillBeRawPtr<Node> nextChild = child->nextSibling();
    349                 removeNode(child.get(), shouldAssumeContentIsAlwaysEditable);
    350                 // Bail if nextChild is no longer node's child.
    351                 if (nextChild && nextChild->parentNode() != node)
    352                     return;
    353                 child = nextChild;
    354             }
    355 
    356             // Don't remove editable regions that are inside non-editable ones, just clear them.
    357             return;
    358         }
    359     }
    360 
    361     if (isTableStructureNode(node.get()) || node->isRootEditableElement()) {
    362         // Do not remove an element of table structure; remove its contents.
    363         // Likewise for the root editable element.
    364         Node* child = node->firstChild();
    365         while (child) {
    366             Node* remove = child;
    367             child = child->nextSibling();
    368             removeNode(remove, shouldAssumeContentIsAlwaysEditable);
    369         }
    370 
    371         // Make sure empty cell has some height, if a placeholder can be inserted.
    372         document().updateLayoutIgnorePendingStylesheets();
    373         RenderObject *r = node->renderer();
    374         if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0) {
    375             Position firstEditablePosition = firstEditablePositionInNode(node.get());
    376             if (firstEditablePosition.isNotNull())
    377                 insertBlockPlaceholder(firstEditablePosition);
    378         }
    379         return;
    380     }
    381 
    382     if (node == m_startBlock) {
    383         VisiblePosition previous = VisiblePosition(firstPositionInNode(m_startBlock.get())).previous();
    384         if (previous.isNotNull() && !isEndOfBlock(previous))
    385             m_needPlaceholder = true;
    386     }
    387     if (node == m_endBlock) {
    388         VisiblePosition next = VisiblePosition(lastPositionInNode(m_endBlock.get())).next();
    389         if (next.isNotNull() && !isStartOfBlock(next))
    390             m_needPlaceholder = true;
    391     }
    392 
    393     // FIXME: Update the endpoints of the range being deleted.
    394     updatePositionForNodeRemoval(m_endingPosition, *node);
    395     updatePositionForNodeRemoval(m_leadingWhitespace, *node);
    396     updatePositionForNodeRemoval(m_trailingWhitespace, *node);
    397 
    398     CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable);
    399 }
    400 
    401 static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
    402 {
    403     if (position.anchorType() != Position::PositionIsOffsetInAnchor || position.containerNode() != node)
    404         return;
    405 
    406     if (position.offsetInContainerNode() > offset + count)
    407         position.moveToOffset(position.offsetInContainerNode() - count);
    408     else if (position.offsetInContainerNode() > offset)
    409         position.moveToOffset(offset);
    410 }
    411 
    412 void DeleteSelectionCommand::deleteTextFromNode(PassRefPtrWillBeRawPtr<Text> node, unsigned offset, unsigned count)
    413 {
    414     // FIXME: Update the endpoints of the range being deleted.
    415     updatePositionForTextRemoval(node.get(), offset, count, m_endingPosition);
    416     updatePositionForTextRemoval(node.get(), offset, count, m_leadingWhitespace);
    417     updatePositionForTextRemoval(node.get(), offset, count, m_trailingWhitespace);
    418     updatePositionForTextRemoval(node.get(), offset, count, m_downstreamEnd);
    419 
    420     CompositeEditCommand::deleteTextFromNode(node, offset, count);
    421 }
    422 
    423 void DeleteSelectionCommand::makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss()
    424 {
    425     RefPtrWillBeRawPtr<Range> range = m_selectionToDelete.toNormalizedRange();
    426     RefPtrWillBeRawPtr<Node> node = range->firstNode();
    427     while (node && node != range->pastLastNode()) {
    428         RefPtrWillBeRawPtr<Node> nextNode = NodeTraversal::next(*node);
    429         if ((isHTMLStyleElement(*node) && !(toElement(node)->hasAttribute(scopedAttr))) || isHTMLLinkElement(*node)) {
    430             nextNode = NodeTraversal::nextSkippingChildren(*node);
    431             RefPtrWillBeRawPtr<ContainerNode> rootEditableElement = node->rootEditableElement();
    432             if (rootEditableElement.get()) {
    433                 removeNode(node);
    434                 appendNode(node, rootEditableElement);
    435             }
    436         }
    437         node = nextNode;
    438     }
    439 }
    440 
    441 void DeleteSelectionCommand::handleGeneralDelete()
    442 {
    443     if (m_upstreamStart.isNull())
    444         return;
    445 
    446     int startOffset = m_upstreamStart.deprecatedEditingOffset();
    447     Node* startNode = m_upstreamStart.deprecatedNode();
    448     ASSERT(startNode);
    449 
    450     makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss();
    451 
    452     // Never remove the start block unless it's a table, in which case we won't merge content in.
    453     if (startNode->isSameNode(m_startBlock.get()) && !startOffset && canHaveChildrenForEditing(startNode) && !isHTMLTableElement(*startNode)) {
    454         startOffset = 0;
    455         startNode = NodeTraversal::next(*startNode);
    456         if (!startNode)
    457             return;
    458     }
    459 
    460     if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) {
    461         Text* text = toText(startNode);
    462         if (text->length() > (unsigned)caretMaxOffset(startNode))
    463             deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode));
    464     }
    465 
    466     if (startOffset >= lastOffsetForEditing(startNode)) {
    467         startNode = NodeTraversal::nextSkippingChildren(*startNode);
    468         startOffset = 0;
    469     }
    470 
    471     // Done adjusting the start.  See if we're all done.
    472     if (!startNode)
    473         return;
    474 
    475     if (startNode == m_downstreamEnd.deprecatedNode()) {
    476         if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) {
    477             if (startNode->isTextNode()) {
    478                 // in a text node that needs to be trimmed
    479                 Text* text = toText(startNode);
    480                 deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset);
    481             } else {
    482                 removeChildrenInRange(startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset());
    483                 m_endingPosition = m_upstreamStart;
    484             }
    485         }
    486 
    487         // The selection to delete is all in one node.
    488         if (!startNode->renderer() || (!startOffset && m_downstreamEnd.atLastEditingPositionForNode()))
    489             removeNode(startNode);
    490     }
    491     else {
    492         bool startNodeWasDescendantOfEndNode = m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode());
    493         // The selection to delete spans more than one node.
    494         RefPtrWillBeRawPtr<Node> node(startNode);
    495 
    496         if (startOffset > 0) {
    497             if (startNode->isTextNode()) {
    498                 // in a text node that needs to be trimmed
    499                 Text* text = toText(node);
    500                 deleteTextFromNode(text, startOffset, text->length() - startOffset);
    501                 node = NodeTraversal::next(*node);
    502             } else {
    503                 node = startNode->traverseToChildAt(startOffset);
    504             }
    505         } else if (startNode == m_upstreamEnd.deprecatedNode() && startNode->isTextNode()) {
    506             Text* text = toText(m_upstreamEnd.deprecatedNode());
    507             deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset());
    508         }
    509 
    510         // handle deleting all nodes that are completely selected
    511         while (node && node != m_downstreamEnd.deprecatedNode()) {
    512             if (comparePositions(firstPositionInOrBeforeNode(node.get()), m_downstreamEnd) >= 0) {
    513                 // NodeTraversal::nextSkippingChildren just blew past the end position, so stop deleting
    514                 node = nullptr;
    515             } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.get())) {
    516                 RefPtrWillBeRawPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(*node);
    517                 // if we just removed a node from the end container, update end position so the
    518                 // check above will work
    519                 updatePositionForNodeRemoval(m_downstreamEnd, *node);
    520                 removeNode(node.get());
    521                 node = nextNode.get();
    522             } else {
    523                 Node& n = node->lastDescendantOrSelf();
    524                 if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(&n)) {
    525                     removeNode(node.get());
    526                     node = nullptr;
    527                 } else {
    528                     node = NodeTraversal::next(*node);
    529                 }
    530             }
    531         }
    532 
    533         if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()) && m_downstreamEnd.inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode())) {
    534             if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.deprecatedNode())) {
    535                 // The node itself is fully selected, not just its contents.  Delete it.
    536                 removeNode(m_downstreamEnd.deprecatedNode());
    537             } else {
    538                 if (m_downstreamEnd.deprecatedNode()->isTextNode()) {
    539                     // in a text node that needs to be trimmed
    540                     Text* text = toText(m_downstreamEnd.deprecatedNode());
    541                     if (m_downstreamEnd.deprecatedEditingOffset() > 0) {
    542                         deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset());
    543                     }
    544                 // Remove children of m_downstreamEnd.deprecatedNode() that come after m_upstreamStart.
    545                 // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.deprecatedNode()
    546                 // and m_upstreamStart has been removed from the document, because then we don't
    547                 // know how many children to remove.
    548                 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can
    549                 // always know which children to remove.
    550                 } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.inDocument())) {
    551                     int offset = 0;
    552                     if (m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())) {
    553                         Node* n = m_upstreamStart.deprecatedNode();
    554                         while (n && n->parentNode() != m_downstreamEnd.deprecatedNode())
    555                             n = n->parentNode();
    556                         if (n)
    557                             offset = n->nodeIndex() + 1;
    558                     }
    559                     removeChildrenInRange(m_downstreamEnd.deprecatedNode(), offset, m_downstreamEnd.deprecatedEditingOffset());
    560                     m_downstreamEnd = createLegacyEditingPosition(m_downstreamEnd.deprecatedNode(), offset);
    561                 }
    562             }
    563         }
    564     }
    565 }
    566 
    567 void DeleteSelectionCommand::fixupWhitespace()
    568 {
    569     document().updateLayoutIgnorePendingStylesheets();
    570     // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
    571     if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && m_leadingWhitespace.deprecatedNode()->isTextNode()) {
    572         Text* textNode = toText(m_leadingWhitespace.deprecatedNode());
    573         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
    574         replaceTextInNodePreservingMarkers(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
    575     }
    576     if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && m_trailingWhitespace.deprecatedNode()->isTextNode()) {
    577         Text* textNode = toText(m_trailingWhitespace.deprecatedNode());
    578         ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
    579         replaceTextInNodePreservingMarkers(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
    580     }
    581 }
    582 
    583 // If a selection starts in one block and ends in another, we have to merge to bring content before the
    584 // start together with content after the end.
    585 void DeleteSelectionCommand::mergeParagraphs()
    586 {
    587     if (!m_mergeBlocksAfterDelete) {
    588         if (m_pruneStartBlockIfNecessary) {
    589             // We aren't going to merge into the start block, so remove it if it's empty.
    590             prune(m_startBlock);
    591             // Removing the start block during a deletion is usually an indication that we need
    592             // a placeholder, but not in this case.
    593             m_needPlaceholder = false;
    594         }
    595         return;
    596     }
    597 
    598     // It shouldn't have been asked to both try and merge content into the start block and prune it.
    599     ASSERT(!m_pruneStartBlockIfNecessary);
    600 
    601     // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
    602     if (!m_downstreamEnd.inDocument() || !m_upstreamStart.inDocument())
    603          return;
    604 
    605     // FIXME: The deletion algorithm shouldn't let this happen.
    606     if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0)
    607         return;
    608 
    609     // There's nothing to merge.
    610     if (m_upstreamStart == m_downstreamEnd)
    611         return;
    612 
    613     VisiblePosition startOfParagraphToMove(m_downstreamEnd);
    614     VisiblePosition mergeDestination(m_upstreamStart);
    615 
    616     // m_downstreamEnd's block has been emptied out by deletion.  There is no content inside of it to
    617     // move, so just remove it.
    618     Element* endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode());
    619     if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) {
    620         removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode()));
    621         return;
    622     }
    623 
    624     // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
    625     if (!mergeDestination.deepEquivalent().deprecatedNode() || (!mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) && (!mergeDestination.deepEquivalent().anchorNode()->firstChild() || !m_upstreamStart.containerNode()->firstChild())) || (m_startsAtEmptyLine && mergeDestination != startOfParagraphToMove)) {
    626         insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
    627         mergeDestination = VisiblePosition(m_upstreamStart);
    628     }
    629 
    630     if (mergeDestination == startOfParagraphToMove)
    631         return;
    632 
    633     VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove, CanSkipOverEditingBoundary);
    634 
    635     if (mergeDestination == endOfParagraphToMove)
    636         return;
    637 
    638     // If the merge destination and source to be moved are both list items of different lists, merge them into single list.
    639     Node* listItemInFirstParagraph = enclosingNodeOfType(m_upstreamStart, isListItem);
    640     Node* listItemInSecondParagraph = enclosingNodeOfType(m_downstreamEnd, isListItem);
    641     if (listItemInFirstParagraph && listItemInSecondParagraph
    642         && listItemInFirstParagraph->parentElement() != listItemInSecondParagraph->parentElement()
    643         && canMergeLists(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement())) {
    644         mergeIdenticalElements(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement());
    645         m_endingPosition = mergeDestination.deepEquivalent();
    646         return;
    647     }
    648 
    649     // The rule for merging into an empty block is: only do so if its farther to the right.
    650     // FIXME: Consider RTL.
    651     if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
    652         if (isHTMLBRElement(*mergeDestination.deepEquivalent().downstream().deprecatedNode())) {
    653             removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().deprecatedNode());
    654             m_endingPosition = startOfParagraphToMove.deepEquivalent();
    655             return;
    656         }
    657     }
    658 
    659     // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination.  If there is
    660     // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the caret to just before the selection we deleted.
    661     // See https://bugs.webkit.org/show_bug.cgi?id=25439
    662     if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) {
    663         m_endingPosition = m_upstreamStart;
    664         return;
    665     }
    666 
    667     // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block
    668     // removals that it does cause the insertion of *another* placeholder.
    669     bool needPlaceholder = m_needPlaceholder;
    670     bool paragraphToMergeIsEmpty = (startOfParagraphToMove == endOfParagraphToMove);
    671     moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, false, !paragraphToMergeIsEmpty);
    672     m_needPlaceholder = needPlaceholder;
    673     // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
    674     m_endingPosition = endingSelection().start();
    675 }
    676 
    677 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows()
    678 {
    679     if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow) {
    680         Node* row = m_endTableRow->previousSibling();
    681         while (row && row != m_startTableRow) {
    682             RefPtrWillBeRawPtr<Node> previousRow = row->previousSibling();
    683             if (isTableRowEmpty(row))
    684                 // Use a raw removeNode, instead of DeleteSelectionCommand's, because
    685                 // that won't remove rows, it only empties them in preparation for this function.
    686                 CompositeEditCommand::removeNode(row);
    687             row = previousRow.get();
    688         }
    689     }
    690 
    691     // Remove empty rows after the start row.
    692     if (m_startTableRow && m_startTableRow->inDocument() && m_startTableRow != m_endTableRow) {
    693         Node* row = m_startTableRow->nextSibling();
    694         while (row && row != m_endTableRow) {
    695             RefPtrWillBeRawPtr<Node> nextRow = row->nextSibling();
    696             if (isTableRowEmpty(row))
    697                 CompositeEditCommand::removeNode(row);
    698             row = nextRow.get();
    699         }
    700     }
    701 
    702     if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow)
    703         if (isTableRowEmpty(m_endTableRow.get())) {
    704             // Don't remove m_endTableRow if it's where we're putting the ending selection.
    705             if (!m_endingPosition.deprecatedNode()->isDescendantOf(m_endTableRow.get())) {
    706                 // FIXME: We probably shouldn't remove m_endTableRow unless it's fully selected, even if it is empty.
    707                 // We'll need to start adjusting the selection endpoints during deletion to know whether or not m_endTableRow
    708                 // was fully selected here.
    709                 CompositeEditCommand::removeNode(m_endTableRow.get());
    710             }
    711         }
    712 }
    713 
    714 void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
    715 {
    716     // Clearing any previously set typing style and doing an early return.
    717     if (!m_typingStyle) {
    718         document().frame()->selection().clearTypingStyle();
    719         return;
    720     }
    721 
    722     // Compute the difference between the style before the delete and the style now
    723     // after the delete has been done. Set this style on the frame, so other editing
    724     // commands being composed with this one will work, and also cache it on the command,
    725     // so the LocalFrame::appliedEditing can set it after the whole composite command
    726     // has completed.
    727 
    728     // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
    729     if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary))
    730         m_typingStyle = m_deleteIntoBlockquoteStyle;
    731     m_deleteIntoBlockquoteStyle = nullptr;
    732 
    733     m_typingStyle->prepareToApplyAt(m_endingPosition);
    734     if (m_typingStyle->isEmpty())
    735         m_typingStyle = nullptr;
    736     // This is where we've deleted all traces of a style but not a whole paragraph (that's handled above).
    737     // In this case if we start typing, the new characters should have the same style as the just deleted ones,
    738     // but, if we change the selection, come back and start typing that style should be lost.  Also see
    739     // preserveTypingStyle() below.
    740     document().frame()->selection().setTypingStyle(m_typingStyle);
    741 }
    742 
    743 void DeleteSelectionCommand::clearTransientState()
    744 {
    745     m_selectionToDelete = VisibleSelection();
    746     m_upstreamStart.clear();
    747     m_downstreamStart.clear();
    748     m_upstreamEnd.clear();
    749     m_downstreamEnd.clear();
    750     m_endingPosition.clear();
    751     m_leadingWhitespace.clear();
    752     m_trailingWhitespace.clear();
    753 }
    754 
    755 // This method removes div elements with no attributes that have only one child or no children at all.
    756 void DeleteSelectionCommand::removeRedundantBlocks()
    757 {
    758     Node* node = m_endingPosition.containerNode();
    759     Node* rootNode = node->rootEditableElement();
    760 
    761     while (node != rootNode) {
    762         if (isRemovableBlock(node)) {
    763             if (node == m_endingPosition.anchorNode())
    764                 updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node);
    765 
    766             CompositeEditCommand::removeNodePreservingChildren(node);
    767             node = m_endingPosition.anchorNode();
    768         } else
    769             node = node->parentNode();
    770     }
    771 }
    772 
    773 void DeleteSelectionCommand::doApply()
    774 {
    775     // If selection has not been set to a custom selection when the command was created,
    776     // use the current ending selection.
    777     if (!m_hasSelectionToDelete)
    778         m_selectionToDelete = endingSelection();
    779 
    780     if (!m_selectionToDelete.isNonOrphanedRange())
    781         return;
    782 
    783     // save this to later make the selection with
    784     EAffinity affinity = m_selectionToDelete.affinity();
    785 
    786     Position downstreamEnd = m_selectionToDelete.end().downstream();
    787     bool rootWillStayOpenWithoutPlaceholder = downstreamEnd.containerNode() == downstreamEnd.containerNode()->rootEditableElement()
    788         || (downstreamEnd.containerNode()->isTextNode() && downstreamEnd.containerNode()->parentNode() == downstreamEnd.containerNode()->rootEditableElement());
    789     bool lineBreakAtEndOfSelectionToDelete = lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd());
    790     m_needPlaceholder = !rootWillStayOpenWithoutPlaceholder
    791         && isStartOfParagraph(m_selectionToDelete.visibleStart(), CanCrossEditingBoundary)
    792         && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditingBoundary)
    793         && !lineBreakAtEndOfSelectionToDelete;
    794     if (m_needPlaceholder) {
    795         // Don't need a placeholder when deleting a selection that starts just before a table
    796         // and ends inside it (we do need placeholders to hold open empty cells, but that's
    797         // handled elsewhere).
    798         if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart()))
    799             if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table))
    800                 m_needPlaceholder = false;
    801     }
    802 
    803 
    804     // set up our state
    805     initializePositionData();
    806 
    807     bool lineBreakBeforeStart = lineBreakExistsAtVisiblePosition(VisiblePosition(m_upstreamStart).previous());
    808 
    809     // Delete any text that may hinder our ability to fixup whitespace after the delete
    810     deleteInsignificantTextDownstream(m_trailingWhitespace);
    811 
    812     saveTypingStyleState();
    813 
    814     // deleting just a BR is handled specially, at least because we do not
    815     // want to replace it with a placeholder BR!
    816     if (handleSpecialCaseBRDelete()) {
    817         calculateTypingStyleAfterDelete();
    818         setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
    819         clearTransientState();
    820         rebalanceWhitespace();
    821         return;
    822     }
    823 
    824     handleGeneralDelete();
    825 
    826     fixupWhitespace();
    827 
    828     mergeParagraphs();
    829 
    830     removePreviouslySelectedEmptyTableRows();
    831 
    832     if (!m_needPlaceholder && rootWillStayOpenWithoutPlaceholder) {
    833         VisiblePosition visualEnding(m_endingPosition);
    834         bool hasPlaceholder = lineBreakExistsAtVisiblePosition(visualEnding)
    835             && visualEnding.next(CannotCrossEditingBoundary).isNull();
    836         m_needPlaceholder = hasPlaceholder && lineBreakBeforeStart && !lineBreakAtEndOfSelectionToDelete;
    837     }
    838 
    839     RefPtrWillBeRawPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()) : nullptr;
    840 
    841     if (placeholder) {
    842         if (m_sanitizeMarkup)
    843             removeRedundantBlocks();
    844         insertNodeAt(placeholder.get(), m_endingPosition);
    845     }
    846 
    847     rebalanceWhitespaceAt(m_endingPosition);
    848 
    849     calculateTypingStyleAfterDelete();
    850 
    851     setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
    852     clearTransientState();
    853 }
    854 
    855 EditAction DeleteSelectionCommand::editingAction() const
    856 {
    857     // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
    858     // but in that case there's a TypingCommand that supplies the editingAction(), so
    859     // the Undo menu correctly shows "Undo Typing"
    860     return EditActionCut;
    861 }
    862 
    863 // Normally deletion doesn't preserve the typing style that was present before it.  For example,
    864 // type a character, Bold, then delete the character and start typing.  The Bold typing style shouldn't
    865 // stick around.  Deletion should preserve a typing style that *it* sets, however.
    866 bool DeleteSelectionCommand::preservesTypingStyle() const
    867 {
    868     return m_typingStyle;
    869 }
    870 
    871 void DeleteSelectionCommand::trace(Visitor* visitor)
    872 {
    873     visitor->trace(m_selectionToDelete);
    874     visitor->trace(m_upstreamStart);
    875     visitor->trace(m_downstreamStart);
    876     visitor->trace(m_upstreamEnd);
    877     visitor->trace(m_downstreamEnd);
    878     visitor->trace(m_endingPosition);
    879     visitor->trace(m_leadingWhitespace);
    880     visitor->trace(m_trailingWhitespace);
    881     visitor->trace(m_startBlock);
    882     visitor->trace(m_endBlock);
    883     visitor->trace(m_typingStyle);
    884     visitor->trace(m_deleteIntoBlockquoteStyle);
    885     visitor->trace(m_startRoot);
    886     visitor->trace(m_endRoot);
    887     visitor->trace(m_startTableRow);
    888     visitor->trace(m_endTableRow);
    889     visitor->trace(m_temporaryPlaceholder);
    890     CompositeEditCommand::trace(visitor);
    891 }
    892 
    893 } // namespace WebCore
    894