faf8562e3802bdce875c9071ece46142e7e59a73
[WebKit-https.git] / WebCore / editing / DeleteSelectionCommand.cpp
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 "Editor.h"
32 #include "EditorClient.h"
33 #include "Element.h"
34 #include "Frame.h"
35 #include "Logging.h"
36 #include "CSSComputedStyleDeclaration.h"
37 #include "htmlediting.h"
38 #include "HTMLInputElement.h"
39 #include "HTMLNames.h"
40 #include "markup.h"
41 #include "ReplaceSelectionCommand.h"
42 #include "Text.h"
43 #include "TextIterator.h"
44 #include "visible_units.h"
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 static bool isTableCell(Node* node)
51 {
52     return node && (node->hasTagName(tdTag) || node->hasTagName(thTag));
53 }
54
55 static bool isTableRow(Node* node)
56 {
57     return node && node->hasTagName(trTag);
58 }
59
60 static bool isTableCellEmpty(Node* cell)
61 {
62     ASSERT(isTableCell(cell));
63     VisiblePosition firstInCell(Position(cell, 0));
64     VisiblePosition lastInCell(Position(cell, maxDeepOffset(cell)));
65     return firstInCell == lastInCell;
66 }
67
68 static bool isTableRowEmpty(Node* row)
69 {
70     if (!isTableRow(row))
71         return false;
72         
73     for (Node* child = row->firstChild(); child; child = child->nextSibling())
74         if (isTableCell(child) && !isTableCellEmpty(child))
75             return false;
76     
77     return true;
78 }
79
80 DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
81     : CompositeEditCommand(document), 
82       m_hasSelectionToDelete(false), 
83       m_smartDelete(smartDelete), 
84       m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
85       m_replace(replace),
86       m_expandForSpecialElements(expandForSpecialElements),
87       m_startBlock(0),
88       m_endBlock(0),
89       m_typingStyle(0),
90       m_deleteIntoBlockquoteStyle(0)
91 {
92 }
93
94 DeleteSelectionCommand::DeleteSelectionCommand(const Selection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
95     : CompositeEditCommand(selection.start().node()->document()), 
96       m_hasSelectionToDelete(true), 
97       m_smartDelete(smartDelete), 
98       m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
99       m_replace(replace),
100       m_expandForSpecialElements(expandForSpecialElements),
101       m_selectionToDelete(selection),
102       m_startBlock(0),
103       m_endBlock(0),
104       m_typingStyle(0),
105       m_deleteIntoBlockquoteStyle(0)
106 {
107 }
108
109 void 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.node()->hasTagName(hrTag))
120         start = Position(start.node(), 0);
121     else if (end.node()->hasTagName(hrTag))
122         end = Position(end.node(), 1);
123     
124     // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expanion.
125     if (!m_expandForSpecialElements)
126         return;
127     
128     while (VisiblePosition(start) == m_selectionToDelete.visibleStart() && VisiblePosition(end) == m_selectionToDelete.visibleEnd()) {
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 we're going to expand to include the startSpecialContainer, it must be fully selected.
139         if (startSpecialContainer && !endSpecialContainer && Range::compareBoundaryPoints(positionAfterNode(startSpecialContainer), end) > -1)
140             break;
141
142         // If we're going to expand to include the endSpecialContainer, it must be fully selected.         
143         if (endSpecialContainer && !startSpecialContainer && Range::compareBoundaryPoints(start, positionBeforeNode(endSpecialContainer)) > -1)
144             break;
145         
146         if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer))
147             // Don't adjust the end yet, it is the end of a special element that contains the start
148             // special element (which may or may not be fully selected).
149             start = s;
150         else if (endSpecialContainer && endSpecialContainer->isDescendantOf(startSpecialContainer))
151             // Don't adjust the start yet, it is the start of a special element that contains the end
152             // special element (which may or may not be fully selected).
153             end = e;
154         else {
155             start = s;
156             end = e;
157         }
158     }
159 }
160
161 void DeleteSelectionCommand::initializePositionData()
162 {
163     Position start, end;
164     initializeStartEnd(start, end);
165     
166     m_upstreamStart = start.upstream();
167     m_downstreamStart = start.downstream();
168     m_upstreamEnd = end.upstream();
169     m_downstreamEnd = end.downstream();
170     
171     m_startRoot = editableRootForPosition(start);
172     m_endRoot = editableRootForPosition(end);
173     
174     m_startTableRow = enclosingNodeOfType(start.node(), &isTableRow);
175     m_endTableRow = enclosingNodeOfType(end.node(), &isTableRow);
176     
177     Node* startCell = enclosingTableCell(m_upstreamStart);
178     Node* endCell = enclosingTableCell(m_downstreamEnd);
179     // Don't move content out of a table cell.
180     // FIXME: This isn't right.  A borderless table with two rows and a single column would appear as two paragraphs.
181     if (endCell && endCell != startCell)
182         m_mergeBlocksAfterDelete = false;
183     
184     // Usually the start and the end of the selection to delete are pulled together as a result of the deletion.
185     // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret 
186     // and receive the placeholder after deletion.
187     VisiblePosition visibleEnd(m_downstreamEnd);
188     if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd))
189         m_endingPosition = m_downstreamEnd;
190     else
191         m_endingPosition = m_downstreamStart;
192
193     // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
194     m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity());
195     m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
196
197     if (m_smartDelete) {
198     
199         // skip smart delete if the selection to delete already starts or ends with whitespace
200         Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent();
201         bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
202         if (!skipSmartDelete)
203             skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
204
205         // extend selection upstream if there is whitespace there
206         bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull();
207         if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
208             VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous();
209             pos = visiblePos.deepEquivalent();
210             // Expand out one character upstream for smart delete and recalculate
211             // positions based on this change.
212             m_upstreamStart = pos.upstream();
213             m_downstreamStart = pos.downstream();
214             m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
215         }
216         
217         // trailing whitespace is only considered for smart delete if there is no leading
218         // whitespace, as in the case where you double-click the first word of a paragraph.
219         if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
220             // Expand out one character downstream for smart delete and recalculate
221             // positions based on this change.
222             pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent();
223             m_upstreamEnd = pos.upstream();
224             m_downstreamEnd = pos.downstream();
225             m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
226         }
227     }
228     
229     //
230     // Handle setting start and end blocks and the start node.
231     //
232     m_startBlock = m_downstreamStart.node()->enclosingBlockFlowOrTableElement();
233     m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowOrTableElement();
234 }
235
236 void DeleteSelectionCommand::saveTypingStyleState()
237 {
238     // Figure out the typing style in effect before the delete is done.
239     // FIXME: Improve typing style.
240     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
241     RefPtr<CSSComputedStyleDeclaration> computedStyle = positionBeforeTabSpan(m_selectionToDelete.start()).computedStyle();
242     m_typingStyle = computedStyle->copyInheritableProperties();
243     
244     // If we're deleting into a Mail blockquote, save the style at end() instead of start()
245     // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
246     if (nearestMailBlockquote(m_selectionToDelete.start().node())) {
247         computedStyle = m_selectionToDelete.end().computedStyle();
248         m_deleteIntoBlockquoteStyle = computedStyle->copyInheritableProperties();
249     } else
250         m_deleteIntoBlockquoteStyle = 0;
251 }
252
253 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
254 {
255     // Check for special-case where the selection contains only a BR on a line by itself after another BR.
256     bool upstreamStartIsBR = m_upstreamStart.node()->hasTagName(brTag);
257     bool downstreamStartIsBR = m_downstreamStart.node()->hasTagName(brTag);
258     bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
259     if (isBROnLineByItself) {
260         removeNode(m_downstreamStart.node());
261         return true;
262     }
263
264     // Not a special-case delete per se, but we can detect that the merging of content between blocks
265     // should not be done.
266     if (upstreamStartIsBR && downstreamStartIsBR) {
267         m_mergeBlocksAfterDelete = false;
268         m_endingPosition = m_downstreamEnd;
269     }
270     
271     return false;
272 }
273
274 static void updatePositionForNodeRemoval(Node* node, Position& position)
275 {
276     if (position.isNull())
277         return;
278     if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.offset())
279         position = Position(position.node(), position.offset() - 1);
280     if (position.node() == node || position.node()->isDescendantOf(node))
281         position = positionBeforeNode(node);
282 }
283
284 void DeleteSelectionCommand::removeNode(Node *node)
285 {
286     if (!node)
287         return;
288         
289     if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.get()) && node->isDescendantOf(m_endRoot.get()))) {
290         // If a node is not in both the start and end editable roots, remove it only if its inside an editable region.
291         if (!node->parentNode()->isContentEditable()) {
292             // Don't remove non-editable atomic nodes.
293             if (!node->firstChild())
294                 return;
295             // Search this non-editable region for editable regions to empty.
296             RefPtr<Node> child = node->firstChild();
297             while (child) {
298                 RefPtr<Node> nextChild = child->nextSibling();
299                 removeNode(child.get());
300                 // Bail if nextChild is no longer node's child.
301                 if (nextChild && nextChild->parentNode() != node)
302                     return;
303                 child = nextChild;
304             }
305             
306             // Don't remove editable regions that are inside non-editable ones, just clear them.
307             return;
308         }
309     }
310     
311     if (isTableStructureNode(node) || node == node->rootEditableElement()) {
312         // Do not remove an element of table structure; remove its contents.
313         // Likewise for the root editable element.
314         Node *child = node->firstChild();
315         while (child) {
316             Node *remove = child;
317             child = child->nextSibling();
318             removeNode(remove);
319         }
320         
321         // make sure empty cell has some height
322         updateLayout();
323         RenderObject *r = node->renderer();
324         if (r && r->isTableCell() && r->contentHeight() <= 0)
325             insertBlockPlaceholder(Position(node,0));
326         return;
327     }
328     
329     if (node == m_startBlock && !isEndOfBlock(VisiblePosition(m_startBlock.get(), 0, DOWNSTREAM).previous()))
330         m_needPlaceholder = true;
331     else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(m_endBlock.get(), maxDeepOffset(m_endBlock.get()), DOWNSTREAM).next()))
332         m_needPlaceholder = true;
333     
334     // FIXME: Update the endpoints of the range being deleted.
335     updatePositionForNodeRemoval(node, m_endingPosition);
336     updatePositionForNodeRemoval(node, m_leadingWhitespace);
337     updatePositionForNodeRemoval(node, m_trailingWhitespace);
338     
339     CompositeEditCommand::removeNode(node);
340 }
341
342
343 void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
344 {
345     if (position.node() == node) {
346         if (position.offset() > offset + count)
347             position = Position(position.node(), position.offset() - count);
348         else if (position.offset() > offset)
349             position = Position(position.node(), offset);
350     }
351 }
352
353 void DeleteSelectionCommand::deleteTextFromNode(Text *node, int offset, int count)
354 {
355     // FIXME: Update the endpoints of the range being deleted.
356     updatePositionForTextRemoval(node, offset, count, m_endingPosition);
357     updatePositionForTextRemoval(node, offset, count, m_leadingWhitespace);
358     updatePositionForTextRemoval(node, offset, count, m_trailingWhitespace);
359     
360     CompositeEditCommand::deleteTextFromNode(node, offset, count);
361 }
362
363 void DeleteSelectionCommand::handleGeneralDelete()
364 {
365     int startOffset = m_upstreamStart.offset();
366     Node* startNode = m_upstreamStart.node();
367     
368     // Never remove the start block unless it's a table, in which case we won't merge content in.
369     if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditing(startNode) && !startNode->hasTagName(tableTag)) {
370         startOffset = 0;
371         startNode = startNode->traverseNextNode();
372     }
373
374     if (startOffset >= startNode->caretMaxOffset() && startNode->isTextNode()) {
375         Text *text = static_cast<Text *>(startNode);
376         if (text->length() > (unsigned)startNode->caretMaxOffset())
377             deleteTextFromNode(text, startNode->caretMaxOffset(), text->length() - startNode->caretMaxOffset());
378     }
379
380     if (startOffset >= maxDeepOffset(startNode)) {
381         startNode = startNode->traverseNextSibling();
382         startOffset = 0;
383     }
384
385     // Done adjusting the start.  See if we're all done.
386     if (!startNode)
387         return;
388
389     if (startNode == m_downstreamEnd.node()) {
390         // The selection to delete is all in one node.
391         if (!startNode->renderer() || 
392             (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(startNode))) {
393             // just delete
394             removeNode(startNode);
395         } else if (m_downstreamEnd.offset() - startOffset > 0) {
396             if (startNode->isTextNode()) {
397                 // in a text node that needs to be trimmed
398                 Text *text = static_cast<Text *>(startNode);
399                 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
400             } else {
401                 removeChildrenInRange(startNode, startOffset, m_downstreamEnd.offset());
402                 m_endingPosition = m_upstreamStart;
403             }
404         }
405     }
406     else {
407         bool startNodeWasDescendantOfEndNode = m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node());
408         // The selection to delete spans more than one node.
409         RefPtr<Node> node(startNode);
410         
411         if (startOffset > 0) {
412             if (startNode->isTextNode()) {
413                 // in a text node that needs to be trimmed
414                 Text *text = static_cast<Text *>(node.get());
415                 deleteTextFromNode(text, startOffset, text->length() - startOffset);
416                 node = node->traverseNextNode();
417             } else {
418                 node = startNode->childNode(startOffset);
419             }
420         }
421         
422         // handle deleting all nodes that are completely selected
423         while (node && node != m_downstreamEnd.node()) {
424             if (Range::compareBoundaryPoints(Position(node.get(), 0), m_downstreamEnd) >= 0) {
425                 // traverseNextSibling just blew past the end position, so stop deleting
426                 node = 0;
427             } else if (!m_downstreamEnd.node()->isDescendantOf(node.get())) {
428                 RefPtr<Node> nextNode = node->traverseNextSibling();
429                 // if we just removed a node from the end container, update end position so the
430                 // check above will work
431                 if (node->parentNode() == m_downstreamEnd.node()) {
432                     ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
433                     m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
434                 }
435                 removeNode(node.get());
436                 node = nextNode.get();
437             } else {
438                 Node* n = node->lastDescendant();
439                 if (m_downstreamEnd.node() == n && m_downstreamEnd.offset() >= n->caretMaxOffset()) {
440                     removeNode(node.get());
441                     node = 0;
442                 } else
443                     node = node->traverseNextNode();
444             }
445         }
446         
447         if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
448             if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node()) && !canHaveChildrenForEditing(m_downstreamEnd.node())) {
449                 // FIXME: Shouldn't remove m_downstreamEnd.node() if its offsets refer to children. 
450                 // The node itself is fully selected, not just its contents.  Delete it.
451                 removeNode(m_downstreamEnd.node());
452             } else {
453                 if (m_downstreamEnd.node()->isTextNode()) {
454                     // in a text node that needs to be trimmed
455                     Text *text = static_cast<Text *>(m_downstreamEnd.node());
456                     if (m_downstreamEnd.offset() > 0) {
457                         deleteTextFromNode(text, 0, m_downstreamEnd.offset());
458                         m_downstreamEnd = Position(text, 0);
459                     }
460                 // Remove children of m_downstreamEnd.node() that come after m_upstreamStart.
461                 // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.node()
462                 // and m_upstreamStart has been removed from the document, because then we don't 
463                 // know how many children to remove.
464                 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can
465                 // always know which children to remove.
466                 } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.node()->inDocument())) {
467                     int offset = 0;
468                     if (m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node())) {
469                         Node *n = m_upstreamStart.node();
470                         while (n && n->parentNode() != m_downstreamEnd.node())
471                             n = n->parentNode();
472                         if (n)
473                             offset = n->nodeIndex() + 1;
474                     }
475                     removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
476                     m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
477                 }
478             }
479         }
480     }
481 }
482
483 void DeleteSelectionCommand::fixupWhitespace()
484 {
485     updateLayout();
486     // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
487     if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter()) {
488         Text* textNode = static_cast<Text*>(m_leadingWhitespace.node());
489         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
490         replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
491     }
492     if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter()) {
493         Text* textNode = static_cast<Text*>(m_trailingWhitespace.node());
494         ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
495         replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
496     }
497 }
498
499 // If a selection starts in one block and ends in another, we have to merge to bring content before the
500 // start together with content after the end.
501 void DeleteSelectionCommand::mergeParagraphs()
502 {
503     if (!m_mergeBlocksAfterDelete)
504         return;
505
506     // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
507     if (!m_downstreamEnd.node()->inDocument() || !m_upstreamStart.node()->inDocument())
508          return;
509          
510     // FIXME: The deletion algorithm shouldn't let this happen.
511     if (Range::compareBoundaryPoints(m_upstreamStart, m_downstreamEnd) > 0)
512         return;
513         
514     // FIXME: Merging will always be unnecessary in this case, but we really bail here because this is a case where
515     // deletion commonly fails to adjust its endpoints, which would cause the visible position comparison below to false negative.
516     if (m_endBlock == m_startBlock)
517         return;
518         
519     VisiblePosition startOfParagraphToMove(m_downstreamEnd);
520     VisiblePosition mergeDestination(m_upstreamStart);
521     
522     // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
523     if (!mergeDestination.deepEquivalent().node() || !mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement())) {
524         insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
525         mergeDestination = VisiblePosition(m_upstreamStart);
526     }
527     
528     if (mergeDestination == startOfParagraphToMove)
529         return;
530         
531     VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove);
532     
533     if (mergeDestination == endOfParagraphToMove)
534         return;
535     
536     // The rule for merging into an empty block is: only do so if its farther to the right.
537     // FIXME: Consider RTL.
538     // FIXME: handleSpecialCaseBRDelete prevents us from getting here in a case like <ul><li>foo<br><br></li></ul>^foo
539     if (isStartOfParagraph(mergeDestination) && 
540         startOfParagraphToMove.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x() >
541         mergeDestination.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x()) {
542         ASSERT(mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag));
543         removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node());
544         m_endingPosition = startOfParagraphToMove.deepEquivalent();
545         return;
546     }
547     
548     RefPtr<Range> range = new Range(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(endOfParagraphToMove.deepEquivalent()));
549     RefPtr<Range> rangeToBeReplaced = new Range(document(), rangeCompliantEquivalent(mergeDestination.deepEquivalent()), rangeCompliantEquivalent(mergeDestination.deepEquivalent()));
550     if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(range.get(), rangeToBeReplaced.get()))
551         return;
552     
553     moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination);
554     // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
555     m_endingPosition = endingSelection().start();
556 }
557
558 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows()
559 {
560     if (m_endTableRow && m_endTableRow->inDocument()) {
561         Node* row = m_endTableRow.get();
562         // Do not remove the row that contained the start of the selection,
563         // since it now contains the selection.
564         while (row && row != m_startTableRow.get()) {
565             RefPtr<Node> previousRow = row->previousSibling();
566             if (isTableRowEmpty(row))
567                 // Use a raw removeNode, instead of DeleteSelectionCommand's, because
568                 // that won't remove rows, it only empties them in preparation for this function.
569                 CompositeEditCommand::removeNode(row);
570             row = previousRow.get();
571         }
572     }
573     
574     if (m_startTableRow && m_startTableRow->inDocument()) {
575         // Do not remove the row that contained the start of the selection,
576         // since it now contains the selection.
577         Node* row = m_startTableRow->nextSibling();
578         while (row) {
579             RefPtr<Node> nextRow = row->nextSibling();
580             if (isTableRowEmpty(row))
581                 CompositeEditCommand::removeNode(row);
582             row = nextRow.get();
583         }
584     }
585 }
586
587 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(Node *insertedPlaceholder)
588 {
589     // Compute the difference between the style before the delete and the style now
590     // after the delete has been done. Set this style on the frame, so other editing
591     // commands being composed with this one will work, and also cache it on the command,
592     // so the Frame::appliedEditing can set it after the whole composite command 
593     // has completed.
594     // FIXME: Improve typing style.
595     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
596     
597     // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
598     if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.node()))
599         m_typingStyle = m_deleteIntoBlockquoteStyle;
600     m_deleteIntoBlockquoteStyle = 0;
601     
602     RefPtr<CSSComputedStyleDeclaration> endingStyle = new CSSComputedStyleDeclaration(m_endingPosition.node());
603     endingStyle->diff(m_typingStyle.get());
604     if (!m_typingStyle->length())
605         m_typingStyle = 0;
606     if (insertedPlaceholder && m_typingStyle) {
607         // Apply style to the placeholder. This makes sure that the single line in the
608         // paragraph has the right height, and that the paragraph takes on the style
609         // of the preceding line and retains it even if you click away, click back, and
610         // then start typing. In this case, the typing style is applied right now, and
611         // is not retained until the next typing action.
612
613         setEndingSelection(Selection(Position(insertedPlaceholder, 0), DOWNSTREAM));
614         applyStyle(m_typingStyle.get(), EditActionUnspecified);
615         m_typingStyle = 0;
616     }
617     // Set m_typingStyle as the typing style.
618     // It's perfectly OK for m_typingStyle to be null.
619     document()->frame()->setTypingStyle(m_typingStyle.get());
620     setTypingStyle(m_typingStyle.get());
621 }
622
623 void DeleteSelectionCommand::clearTransientState()
624 {
625     m_selectionToDelete = Selection();
626     m_upstreamStart.clear();
627     m_downstreamStart.clear();
628     m_upstreamEnd.clear();
629     m_downstreamEnd.clear();
630     m_endingPosition.clear();
631     m_leadingWhitespace.clear();
632     m_trailingWhitespace.clear();
633 }
634
635 void DeleteSelectionCommand::saveFullySelectedAnchor()
636 {
637     // If we're deleting an entire anchor element, save it away so that it can be restored
638     // when the user begins entering text.
639     VisiblePosition visibleStart = m_selectionToDelete.visibleStart();
640     VisiblePosition visibleEnd = m_selectionToDelete.visibleEnd();
641     Node* startAnchor = enclosingNodeWithTag(visibleStart.deepEquivalent().downstream().node(), aTag);
642     Node* endAnchor = enclosingNodeWithTag(visibleEnd.deepEquivalent().upstream().node(), aTag);
643
644     Node* beforeStartAnchor = enclosingNodeWithTag(visibleStart.previous().deepEquivalent().downstream().node(), aTag);
645     Node* afterEndAnchor = enclosingNodeWithTag(visibleEnd.next().deepEquivalent().upstream().node(), aTag);
646
647     if (startAnchor && startAnchor == endAnchor && startAnchor != beforeStartAnchor && endAnchor != afterEndAnchor)
648         document()->frame()->editor()->setRemovedAnchor(startAnchor->cloneNode(false));
649 }
650
651 void DeleteSelectionCommand::doApply()
652 {
653     // If selection has not been set to a custom selection when the command was created,
654     // use the current ending selection.
655     if (!m_hasSelectionToDelete)
656         m_selectionToDelete = endingSelection();
657     
658     if (!m_selectionToDelete.isRange())
659         return;
660
661     // 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. 
662     if (!m_replace) {
663         Node* startNode = m_selectionToDelete.start().node();
664         Node* ancestorNode = startNode ? startNode->shadowAncestorNode() : 0;
665         if (ancestorNode && ancestorNode->hasTagName(inputTag)
666                 && static_cast<HTMLInputElement*>(ancestorNode)->isTextField()
667                 && ancestorNode->focused())
668             document()->frame()->textWillBeDeletedInTextField(static_cast<Element*>(ancestorNode));
669     }
670
671     // save this to later make the selection with
672     EAffinity affinity = m_selectionToDelete.affinity();
673     
674     Position downstreamEnd = m_selectionToDelete.end().downstream();
675     m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart()) &&
676                         isEndOfParagraph(m_selectionToDelete.visibleEnd()) &&
677                         !lineBreakExistsAtPosition(m_selectionToDelete.visibleEnd());
678     if (m_needPlaceholder) {
679         // Don't need a placeholder when deleting a selection that starts just before a table
680         // and ends inside it (we do need placeholders to hold open empty cells, but that's
681         // handled elsewhere).
682         if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart()))
683             if (m_selectionToDelete.end().node()->isDescendantOf(table))
684                 m_needPlaceholder = false;
685     }
686         
687     
688     // set up our state
689     initializePositionData();
690     if (!m_startBlock || !m_endBlock) {
691         // Can't figure out what blocks we're in. This can happen if
692         // the document structure is not what we are expecting, like if
693         // the document has no body element, or if the editable block
694         // has been changed to display: inline. Some day it might
695         // be nice to be able to deal with this, but for now, bail.
696         clearTransientState();
697         return;
698     }
699
700     // Delete any text that may hinder our ability to fixup whitespace after the delete
701     deleteInsignificantTextDownstream(m_trailingWhitespace);    
702
703     saveTypingStyleState();
704     
705     saveFullySelectedAnchor();
706     
707     // deleting just a BR is handled specially, at least because we do not
708     // want to replace it with a placeholder BR!
709     if (handleSpecialCaseBRDelete()) {
710         calculateTypingStyleAfterDelete(false);
711         setEndingSelection(Selection(m_endingPosition, affinity));
712         clearTransientState();
713         rebalanceWhitespace();
714         return;
715     }
716     
717     handleGeneralDelete();
718     
719     fixupWhitespace();
720
721     RefPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()) : 0;
722     
723     mergeParagraphs();
724     
725     removePreviouslySelectedEmptyTableRows();
726     
727     if (placeholder)
728         insertNodeAt(placeholder.get(), m_endingPosition);
729
730     rebalanceWhitespaceAt(m_endingPosition);
731
732     calculateTypingStyleAfterDelete(placeholder.get());
733     
734     setEndingSelection(Selection(m_endingPosition, affinity));
735     clearTransientState();
736 }
737
738 EditAction DeleteSelectionCommand::editingAction() const
739 {
740     // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
741     // but in that case there's a TypingCommand that supplies the editingAction(), so
742     // the Undo menu correctly shows "Undo Typing"
743     return EditActionCut;
744 }
745
746 bool DeleteSelectionCommand::preservesTypingStyle() const
747 {
748     return true;
749 }
750
751 } // namespace WebCore