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