1a2f4b9704fbb575db9a6f19b416eae8a0e99477
[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(const 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 >= caretMaxOffset(startNode) && startNode->isTextNode()) {
375         Text *text = static_cast<Text *>(startNode);
376         if (text->length() > (unsigned)caretMaxOffset(startNode))
377             deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode));
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() >= caretMaxOffset(n)) {
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() >= caretMinOffset(m_downstreamEnd.node())) {
448             if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node()) && !canHaveChildrenForEditing(m_downstreamEnd.node())) {
449                 // The node itself is fully selected, not just its contents.  Delete it.
450                 removeNode(m_downstreamEnd.node());
451             } else {
452                 if (m_downstreamEnd.node()->isTextNode()) {
453                     // in a text node that needs to be trimmed
454                     Text *text = static_cast<Text *>(m_downstreamEnd.node());
455                     if (m_downstreamEnd.offset() > 0) {
456                         deleteTextFromNode(text, 0, m_downstreamEnd.offset());
457                         m_downstreamEnd = Position(text, 0);
458                     }
459                 // Remove children of m_downstreamEnd.node() that come after m_upstreamStart.
460                 // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.node()
461                 // and m_upstreamStart has been removed from the document, because then we don't 
462                 // know how many children to remove.
463                 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can
464                 // always know which children to remove.
465                 } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.node()->inDocument())) {
466                     int offset = 0;
467                     if (m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node())) {
468                         Node *n = m_upstreamStart.node();
469                         while (n && n->parentNode() != m_downstreamEnd.node())
470                             n = n->parentNode();
471                         if (n)
472                             offset = n->nodeIndex() + 1;
473                     }
474                     removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
475                     m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
476                 }
477             }
478         }
479     }
480 }
481
482 void DeleteSelectionCommand::fixupWhitespace()
483 {
484     updateLayout();
485     // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
486     if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter()) {
487         Text* textNode = static_cast<Text*>(m_leadingWhitespace.node());
488         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
489         replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
490     }
491     if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter()) {
492         Text* textNode = static_cast<Text*>(m_trailingWhitespace.node());
493         ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
494         replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
495     }
496 }
497
498 // If a selection starts in one block and ends in another, we have to merge to bring content before the
499 // start together with content after the end.
500 void DeleteSelectionCommand::mergeParagraphs()
501 {
502     if (!m_mergeBlocksAfterDelete)
503         return;
504
505     // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
506     if (!m_downstreamEnd.node()->inDocument() || !m_upstreamStart.node()->inDocument())
507          return;
508          
509     // FIXME: The deletion algorithm shouldn't let this happen.
510     if (Range::compareBoundaryPoints(m_upstreamStart, m_downstreamEnd) > 0)
511         return;
512         
513     // FIXME: Merging will always be unnecessary in this case, but we really bail here because this is a case where
514     // deletion commonly fails to adjust its endpoints, which would cause the visible position comparison below to false negative.
515     if (m_endBlock == m_startBlock)
516         return;
517         
518     VisiblePosition startOfParagraphToMove(m_downstreamEnd);
519     VisiblePosition mergeDestination(m_upstreamStart);
520     
521     // m_downstreamEnd's block has been emptied out by deletion.  There is no content inside of it to
522     // move, so just remove it.
523     Element* endBlock = static_cast<Element*>(enclosingBlock(m_downstreamEnd.node()));
524     if (!startOfParagraphToMove.deepEquivalent().node() || !endBlock->contains(startOfParagraphToMove.deepEquivalent().node())) {
525         removeNode(enclosingBlock(m_downstreamEnd.node()));
526         return;
527     }
528     
529     // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
530     if (!mergeDestination.deepEquivalent().node() || !mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement())) {
531         insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
532         mergeDestination = VisiblePosition(m_upstreamStart);
533     }
534     
535     if (mergeDestination == startOfParagraphToMove)
536         return;
537         
538     VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove);
539     
540     if (mergeDestination == endOfParagraphToMove)
541         return;
542     
543     // The rule for merging into an empty block is: only do so if its farther to the right.
544     // FIXME: Consider RTL.
545     // FIXME: handleSpecialCaseBRDelete prevents us from getting here in a case like <ul><li>foo<br><br></li></ul>^foo
546     if (isStartOfParagraph(mergeDestination) && 
547         startOfParagraphToMove.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x() >
548         mergeDestination.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x()) {
549         ASSERT(mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag));
550         removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node());
551         m_endingPosition = startOfParagraphToMove.deepEquivalent();
552         return;
553     }
554     
555     RefPtr<Range> range = new Range(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(endOfParagraphToMove.deepEquivalent()));
556     RefPtr<Range> rangeToBeReplaced = new Range(document(), rangeCompliantEquivalent(mergeDestination.deepEquivalent()), rangeCompliantEquivalent(mergeDestination.deepEquivalent()));
557     if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(range.get(), rangeToBeReplaced.get()))
558         return;
559     
560     // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block
561     // removals that it does cause the insertion of *another* placeholder.
562     bool needPlaceholder = m_needPlaceholder;
563     moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination);
564     m_needPlaceholder = needPlaceholder;
565     // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
566     m_endingPosition = endingSelection().start();
567 }
568
569 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows()
570 {
571     if (m_endTableRow && m_endTableRow->inDocument()) {
572         Node* row = m_endTableRow.get();
573         // Do not remove the row that contained the start of the selection,
574         // since it now contains the selection.
575         while (row && row != m_startTableRow.get()) {
576             RefPtr<Node> previousRow = row->previousSibling();
577             if (isTableRowEmpty(row))
578                 // Use a raw removeNode, instead of DeleteSelectionCommand's, because
579                 // that won't remove rows, it only empties them in preparation for this function.
580                 CompositeEditCommand::removeNode(row);
581             row = previousRow.get();
582         }
583     }
584     
585     // Remove empty rows after the start row.
586     if (m_startTableRow && m_startTableRow->inDocument() && m_startTableRow != m_endTableRow) {
587         // Do not remove the row that contained the start of the selection,
588         // since it now contains the selection.
589         Node* row = m_startTableRow->nextSibling();
590         while (row) {
591             RefPtr<Node> nextRow = row->nextSibling();
592             if (isTableRowEmpty(row))
593                 CompositeEditCommand::removeNode(row);
594             row = nextRow.get();
595         }
596     }
597 }
598
599 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(Node *insertedPlaceholder)
600 {
601     // Compute the difference between the style before the delete and the style now
602     // after the delete has been done. Set this style on the frame, so other editing
603     // commands being composed with this one will work, and also cache it on the command,
604     // so the Frame::appliedEditing can set it after the whole composite command 
605     // has completed.
606     // FIXME: Improve typing style.
607     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
608     
609     // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
610     if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.node()))
611         m_typingStyle = m_deleteIntoBlockquoteStyle;
612     m_deleteIntoBlockquoteStyle = 0;
613     
614     RefPtr<CSSComputedStyleDeclaration> endingStyle = new CSSComputedStyleDeclaration(m_endingPosition.node());
615     endingStyle->diff(m_typingStyle.get());
616     if (!m_typingStyle->length())
617         m_typingStyle = 0;
618     if (insertedPlaceholder && m_typingStyle) {
619         // Apply style to the placeholder. This makes sure that the single line in the
620         // paragraph has the right height, and that the paragraph takes on the style
621         // of the preceding line and retains it even if you click away, click back, and
622         // then start typing. In this case, the typing style is applied right now, and
623         // is not retained until the next typing action.
624
625         setEndingSelection(Selection(Position(insertedPlaceholder, 0), DOWNSTREAM));
626         applyStyle(m_typingStyle.get(), EditActionUnspecified);
627         m_typingStyle = 0;
628     }
629     // Set m_typingStyle as the typing style.
630     // It's perfectly OK for m_typingStyle to be null.
631     document()->frame()->setTypingStyle(m_typingStyle.get());
632     setTypingStyle(m_typingStyle.get());
633 }
634
635 void DeleteSelectionCommand::clearTransientState()
636 {
637     m_selectionToDelete = Selection();
638     m_upstreamStart.clear();
639     m_downstreamStart.clear();
640     m_upstreamEnd.clear();
641     m_downstreamEnd.clear();
642     m_endingPosition.clear();
643     m_leadingWhitespace.clear();
644     m_trailingWhitespace.clear();
645 }
646
647 void DeleteSelectionCommand::saveFullySelectedAnchor()
648 {
649     // If we're deleting an entire anchor element, save it away so that it can be restored
650     // when the user begins entering text.
651     VisiblePosition visibleStart = m_selectionToDelete.visibleStart();
652     VisiblePosition visibleEnd = m_selectionToDelete.visibleEnd();
653     Node* startAnchor = enclosingNodeWithTag(visibleStart.deepEquivalent().downstream().node(), aTag);
654     Node* endAnchor = enclosingNodeWithTag(visibleEnd.deepEquivalent().upstream().node(), aTag);
655
656     Node* beforeStartAnchor = enclosingNodeWithTag(visibleStart.previous().deepEquivalent().downstream().node(), aTag);
657     Node* afterEndAnchor = enclosingNodeWithTag(visibleEnd.next().deepEquivalent().upstream().node(), aTag);
658
659     if (startAnchor && startAnchor == endAnchor && startAnchor != beforeStartAnchor && endAnchor != afterEndAnchor)
660         document()->frame()->editor()->setRemovedAnchor(startAnchor->cloneNode(false));
661 }
662
663 void DeleteSelectionCommand::doApply()
664 {
665     // If selection has not been set to a custom selection when the command was created,
666     // use the current ending selection.
667     if (!m_hasSelectionToDelete)
668         m_selectionToDelete = endingSelection();
669     
670     if (!m_selectionToDelete.isRange())
671         return;
672
673     // 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. 
674     if (!m_replace) {
675         Node* startNode = m_selectionToDelete.start().node();
676         Node* ancestorNode = startNode ? startNode->shadowAncestorNode() : 0;
677         if (ancestorNode && ancestorNode->hasTagName(inputTag)
678                 && static_cast<HTMLInputElement*>(ancestorNode)->isTextField()
679                 && ancestorNode->focused())
680             document()->frame()->textWillBeDeletedInTextField(static_cast<Element*>(ancestorNode));
681     }
682
683     // save this to later make the selection with
684     EAffinity affinity = m_selectionToDelete.affinity();
685     
686     Position downstreamEnd = m_selectionToDelete.end().downstream();
687     m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart()) &&
688                         isEndOfParagraph(m_selectionToDelete.visibleEnd()) &&
689                         !lineBreakExistsAtPosition(m_selectionToDelete.visibleEnd());
690     if (m_needPlaceholder) {
691         // Don't need a placeholder when deleting a selection that starts just before a table
692         // and ends inside it (we do need placeholders to hold open empty cells, but that's
693         // handled elsewhere).
694         if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart()))
695             if (m_selectionToDelete.end().node()->isDescendantOf(table))
696                 m_needPlaceholder = false;
697     }
698         
699     
700     // set up our state
701     initializePositionData();
702     if (!m_startBlock || !m_endBlock) {
703         // Can't figure out what blocks we're in. This can happen if
704         // the document structure is not what we are expecting, like if
705         // the document has no body element, or if the editable block
706         // has been changed to display: inline. Some day it might
707         // be nice to be able to deal with this, but for now, bail.
708         clearTransientState();
709         return;
710     }
711
712     // Delete any text that may hinder our ability to fixup whitespace after the delete
713     deleteInsignificantTextDownstream(m_trailingWhitespace);    
714
715     saveTypingStyleState();
716     
717     saveFullySelectedAnchor();
718     
719     // deleting just a BR is handled specially, at least because we do not
720     // want to replace it with a placeholder BR!
721     if (handleSpecialCaseBRDelete()) {
722         calculateTypingStyleAfterDelete(false);
723         setEndingSelection(Selection(m_endingPosition, affinity));
724         clearTransientState();
725         rebalanceWhitespace();
726         return;
727     }
728     
729     handleGeneralDelete();
730     
731     fixupWhitespace();
732     
733     mergeParagraphs();
734     
735     removePreviouslySelectedEmptyTableRows();
736     
737     RefPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()).get() : 0;
738     
739     if (placeholder)
740         insertNodeAt(placeholder.get(), m_endingPosition);
741
742     rebalanceWhitespaceAt(m_endingPosition);
743
744     calculateTypingStyleAfterDelete(placeholder.get());
745     
746     setEndingSelection(Selection(m_endingPosition, affinity));
747     clearTransientState();
748 }
749
750 EditAction DeleteSelectionCommand::editingAction() const
751 {
752     // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
753     // but in that case there's a TypingCommand that supplies the editingAction(), so
754     // the Undo menu correctly shows "Undo Typing"
755     return EditActionCut;
756 }
757
758 bool DeleteSelectionCommand::preservesTypingStyle() const
759 {
760     return true;
761 }
762
763 } // namespace WebCore