LayoutTests:
[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 "Element.h"
32 #include "Frame.h"
33 #include "Logging.h"
34 #include "CSSComputedStyleDeclaration.h"
35 #include "htmlediting.h"
36 #include "HTMLInputElement.h"
37 #include "HTMLNames.h"
38 #include "markup.h"
39 #include "ReplaceSelectionCommand.h"
40 #include "TextIterator.h"
41 #include "visible_units.h"
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace)
48     : CompositeEditCommand(document), 
49       m_hasSelectionToDelete(false), 
50       m_smartDelete(smartDelete), 
51       m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
52       m_replace(replace),
53       m_startBlock(0),
54       m_endBlock(0),
55       m_typingStyle(0),
56       m_deleteIntoBlockquoteStyle(0)
57 {
58 }
59
60 DeleteSelectionCommand::DeleteSelectionCommand(const Selection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace)
61     : CompositeEditCommand(selection.start().node()->document()), 
62       m_hasSelectionToDelete(true), 
63       m_smartDelete(smartDelete), 
64       m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
65       m_replace(replace),
66       m_selectionToDelete(selection),
67       m_startBlock(0),
68       m_endBlock(0),
69       m_typingStyle(0),
70       m_deleteIntoBlockquoteStyle(0)
71 {
72 }
73
74 void DeleteSelectionCommand::initializeStartEnd()
75 {
76     Node* startSpecialContainer = 0;
77     Node* endSpecialContainer = 0;
78  
79     Position start = m_selectionToDelete.start();
80     Position end = m_selectionToDelete.end();
81  
82     // For HR's, we'll get a position at (HR,1) when hitting delete from the beginning of the previous line, or (HR,0) when forward deleting,
83     // but in these cases, we want to delete it, so manually expand the selection
84     if (start.node()->hasTagName(hrTag))
85         start = Position(start.node(), 0);
86     else if (end.node()->hasTagName(hrTag))
87         end = Position(end.node(), 1);
88     
89     while (VisiblePosition(start) == m_selectionToDelete.visibleStart() && VisiblePosition(end) == m_selectionToDelete.visibleEnd()) {
90         startSpecialContainer = 0;
91         endSpecialContainer = 0;
92     
93         Position s = positionOutsideContainingSpecialElement(start, &startSpecialContainer);
94         Position e = positionOutsideContainingSpecialElement(end, &endSpecialContainer);
95         
96         if (!startSpecialContainer || !endSpecialContainer)
97             break;
98         
99         start = s;
100         end = e;
101     }
102  
103     m_upstreamStart = start.upstream();
104     m_downstreamStart = start.downstream();
105     m_upstreamEnd = end.upstream();
106     m_downstreamEnd = end.downstream();
107 }
108
109 void DeleteSelectionCommand::initializePositionData()
110 {
111     initializeStartEnd();
112     
113     Node* startCell = enclosingTableCell(m_upstreamStart.node());
114     Node* endCell = enclosingTableCell(m_downstreamEnd.node());
115     // Don't move content between parts of a table or between table and non-table content.
116     // FIXME: This isn't right.  A borderless table with two rows and a single column would appear as two paragraphs.
117     if ((startCell || endCell) && endCell != startCell)
118         m_mergeBlocksAfterDelete = false;
119     
120     // Usually the start and the end of the selection to delete are pulled together as a result of the deletion.
121     // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret 
122     // and receive the placeholder after deletion.
123     VisiblePosition visibleEnd(m_downstreamEnd);
124     if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd))
125         m_endingPosition = m_downstreamEnd;
126     else
127         m_endingPosition = m_downstreamStart;
128
129     // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
130     m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity());
131     m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
132
133     if (m_smartDelete) {
134     
135         // skip smart delete if the selection to delete already starts or ends with whitespace
136         Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent();
137         bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
138         if (!skipSmartDelete)
139             skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
140
141         // extend selection upstream if there is whitespace there
142         bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull();
143         if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
144             VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous();
145             pos = visiblePos.deepEquivalent();
146             // Expand out one character upstream for smart delete and recalculate
147             // positions based on this change.
148             m_upstreamStart = pos.upstream();
149             m_downstreamStart = pos.downstream();
150             m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
151         }
152         
153         // trailing whitespace is only considered for smart delete if there is no leading
154         // whitespace, as in the case where you double-click the first word of a paragraph.
155         if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
156             // Expand out one character downstream for smart delete and recalculate
157             // positions based on this change.
158             pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent();
159             m_upstreamEnd = pos.upstream();
160             m_downstreamEnd = pos.downstream();
161             m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
162         }
163     }
164     
165     //
166     // Handle setting start and end blocks and the start node.
167     //
168     m_startBlock = m_downstreamStart.node()->enclosingBlockFlowOrTableElement();
169     m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowOrTableElement();
170 }
171
172 void DeleteSelectionCommand::saveTypingStyleState()
173 {
174     // Figure out the typing style in effect before the delete is done.
175     // FIXME: Improve typing style.
176     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
177     RefPtr<CSSComputedStyleDeclaration> computedStyle = positionBeforeTabSpan(m_selectionToDelete.start()).computedStyle();
178     m_typingStyle = computedStyle->copyInheritableProperties();
179     
180     // If we're deleting into a Mail blockquote, save the style at end() instead of start()
181     // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
182     if (nearestMailBlockquote(m_selectionToDelete.start().node())) {
183         computedStyle = m_selectionToDelete.end().computedStyle();
184         m_deleteIntoBlockquoteStyle = computedStyle->copyInheritableProperties();
185     } else
186         m_deleteIntoBlockquoteStyle = 0;
187 }
188
189 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
190 {
191     // Check for special-case where the selection contains only a BR on a line by itself after another BR.
192     bool upstreamStartIsBR = m_upstreamStart.node()->hasTagName(brTag);
193     bool downstreamStartIsBR = m_downstreamStart.node()->hasTagName(brTag);
194     bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
195     if (isBROnLineByItself) {
196         removeNode(m_downstreamStart.node());
197         m_mergeBlocksAfterDelete = false;
198         return true;
199     }
200
201     // Not a special-case delete per se, but we can detect that the merging of content between blocks
202     // should not be done.
203     if (upstreamStartIsBR && downstreamStartIsBR)
204         m_mergeBlocksAfterDelete = false;
205
206     return false;
207 }
208
209 static void updatePositionForNodeRemoval(Node* node, Position& position)
210 {
211     if (position.isNull())
212         return;
213     if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.offset())
214         position = Position(position.node(), position.offset() - 1);
215     if (position.node() == node || position.node()->isDescendantOf(node))
216         position = positionBeforeNode(node);
217 }
218
219 void DeleteSelectionCommand::removeNode(Node *node)
220 {
221     if (isTableStructureNode(node) || node == node->rootEditableElement()) {
222         // Do not remove an element of table structure; remove its contents.
223         // Likewise for the root editable element.
224         Node *child = node->firstChild();
225         while (child) {
226             Node *remove = child;
227             child = child->nextSibling();
228             removeNode(remove);
229         }
230         
231         // make sure empty cell has some height
232         updateLayout();
233         RenderObject *r = node->renderer();
234         if (r && r->isTableCell() && r->contentHeight() <= 0)
235             insertBlockPlaceholder(Position(node,0));
236         return;
237     }
238     
239     if (node == m_startBlock && !isEndOfBlock(VisiblePosition(m_startBlock.get(), 0, DOWNSTREAM).previous()))
240         m_needPlaceholder = true;
241     else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(m_endBlock.get(), maxDeepOffset(m_endBlock.get()), DOWNSTREAM).next()))
242         m_needPlaceholder = true;
243     
244     // FIXME: Update the endpoints of the range being deleted.
245     updatePositionForNodeRemoval(node, m_endingPosition);
246     updatePositionForNodeRemoval(node, m_leadingWhitespace);
247     updatePositionForNodeRemoval(node, m_trailingWhitespace);
248     
249     CompositeEditCommand::removeNode(node);
250 }
251
252
253 void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
254 {
255     if (position.node() == node) {
256         if (position.offset() > offset + count)
257             position = Position(position.node(), position.offset() - count);
258         else if (position.offset() > offset)
259             position = Position(position.node(), offset);
260     }
261 }
262
263 void DeleteSelectionCommand::deleteTextFromNode(Text *node, int offset, int count)
264 {
265     // FIXME: Update the endpoints of the range being deleted.
266     updatePositionForTextRemoval(node, offset, count, m_endingPosition);
267     updatePositionForTextRemoval(node, offset, count, m_leadingWhitespace);
268     updatePositionForTextRemoval(node, offset, count, m_trailingWhitespace);
269     
270     CompositeEditCommand::deleteTextFromNode(node, offset, count);
271 }
272
273 void DeleteSelectionCommand::handleGeneralDelete()
274 {
275     int startOffset = m_upstreamStart.offset();
276     Node* startNode = m_upstreamStart.node();
277     
278     // Never remove the start block unless it's a table, in which case we won't merge content in.
279     if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditing(startNode) && !startNode->hasTagName(tableTag)) {
280         startOffset = 0;
281         startNode = startNode->traverseNextNode();
282     }
283
284     if (startOffset >= startNode->caretMaxOffset() && startNode->isTextNode()) {
285         Text *text = static_cast<Text *>(startNode);
286         if (text->length() > (unsigned)startNode->caretMaxOffset())
287             deleteTextFromNode(text, startNode->caretMaxOffset(), text->length() - startNode->caretMaxOffset());
288     }
289
290     if (startOffset >= maxDeepOffset(startNode)) {
291         startNode = startNode->traverseNextSibling();
292         startOffset = 0;
293     }
294
295     // Done adjusting the start.  See if we're all done.
296     if (!startNode)
297         return;
298
299     if (startNode == m_downstreamEnd.node()) {
300         // The selection to delete is all in one node.
301         if (!startNode->renderer() || 
302             (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(startNode))) {
303             // just delete
304             removeNode(startNode);
305         } else if (m_downstreamEnd.offset() - startOffset > 0) {
306             if (startNode->isTextNode()) {
307                 // in a text node that needs to be trimmed
308                 Text *text = static_cast<Text *>(startNode);
309                 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
310             } else {
311                 removeChildrenInRange(startNode, startOffset, m_downstreamEnd.offset());
312                 m_endingPosition = m_upstreamStart;
313             }
314         }
315     }
316     else {
317         // The selection to delete spans more than one node.
318         Node *node = startNode;
319         
320         if (startOffset > 0) {
321             if (startNode->isTextNode()) {
322                 // in a text node that needs to be trimmed
323                 Text *text = static_cast<Text *>(node);
324                 deleteTextFromNode(text, startOffset, text->length() - startOffset);
325                 node = node->traverseNextNode();
326             } else {
327                 node = startNode->childNode(startOffset);
328             }
329         }
330         
331         // handle deleting all nodes that are completely selected
332         while (node && node != m_downstreamEnd.node()) {
333             if (Range::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
334                 // traverseNextSibling just blew past the end position, so stop deleting
335                 node = 0;
336             } else if (!m_downstreamEnd.node()->isDescendantOf(node)) {
337                 Node *nextNode = node->traverseNextSibling();
338                 // if we just removed a node from the end container, update end position so the
339                 // check above will work
340                 if (node->parentNode() == m_downstreamEnd.node()) {
341                     ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
342                     m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
343                 }
344                 removeNode(node);
345                 node = nextNode;
346             } else {
347                 Node* n = node->lastDescendant();
348                 if (m_downstreamEnd.node() == n && m_downstreamEnd.offset() >= n->caretMaxOffset()) {
349                     removeNode(node);
350                     node = 0;
351                 } else
352                     node = node->traverseNextNode();
353             }
354         }
355         
356         if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
357             if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
358                 // need to delete whole node
359                 // we can get here if this is the last node in the block
360                 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
361                 if (!m_upstreamStart.node()->inDocument() ||
362                     m_upstreamStart.node() == m_downstreamEnd.node() ||
363                     m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node())) {
364                     m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
365                 }
366                 
367                 removeNode(m_downstreamEnd.node());
368             } else {
369                 if (m_downstreamEnd.node()->isTextNode()) {
370                     // in a text node that needs to be trimmed
371                     Text *text = static_cast<Text *>(m_downstreamEnd.node());
372                     if (m_downstreamEnd.offset() > 0) {
373                         deleteTextFromNode(text, 0, m_downstreamEnd.offset());
374                         m_downstreamEnd = Position(text, 0);
375                     }
376                 } else {
377                     int offset = 0;
378                     if (m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node())) {
379                         Node *n = m_upstreamStart.node();
380                         while (n && n->parentNode() != m_downstreamEnd.node())
381                             n = n->parentNode();
382                         if (n)
383                             offset = n->nodeIndex() + 1;
384                     }
385                     removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
386                     m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
387                 }
388             }
389         }
390     }
391 }
392
393 void DeleteSelectionCommand::fixupWhitespace()
394 {
395     updateLayout();
396     // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
397     if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter()) {
398         Text* textNode = static_cast<Text*>(m_leadingWhitespace.node());
399         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
400         replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
401     }
402     if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter()) {
403         Text* textNode = static_cast<Text*>(m_trailingWhitespace.node());
404         ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
405         replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
406     }
407 }
408
409 // If a selection starts in one block and ends in another, we have to merge to bring content before the
410 // start together with content after the end.
411 void DeleteSelectionCommand::mergeParagraphs()
412 {
413     if (!m_mergeBlocksAfterDelete)
414         return;
415
416     // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
417     if (!m_downstreamEnd.node()->inDocument() || !m_upstreamStart.node()->inDocument())
418          return;
419          
420     // FIXME: The deletion algorithm shouldn't let this happen.
421     if (Range::compareBoundaryPoints(m_upstreamStart, m_downstreamEnd) > 0)
422         return;
423         
424     // FIXME: Merging will always be unnecessary in this case, but we really bail here because this is a case where
425     // deletion commonly fails to adjust its endpoints, which would cause the visible position comparison below to false negative.
426     if (m_endBlock == m_startBlock)
427         return;
428         
429     VisiblePosition startOfParagraphToMove(m_downstreamEnd);
430     VisiblePosition mergeDestination(m_upstreamStart);
431     
432     // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
433     if (!mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement())) {
434         insertNodeAt(createBreakElement(document()).get(), m_upstreamStart.node(), m_upstreamStart.offset());
435         mergeDestination = VisiblePosition(m_upstreamStart);
436     }
437     
438     if (mergeDestination == startOfParagraphToMove)
439         return;
440         
441     VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove);
442     
443     if (mergeDestination == endOfParagraphToMove)
444         return;
445     
446     // The rule for merging into an empty block is: only do so if its farther to the right.
447     // FIXME: Consider RTL.
448     // FIXME: handleSpecialCaseBRDelete prevents us from getting here in a case like <ul><li>foo<br><br></li></ul>^foo
449     if (isStartOfParagraph(mergeDestination) && 
450         startOfParagraphToMove.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x() >
451         mergeDestination.deepEquivalent().node()->renderer()->caretRect(startOfParagraphToMove.deepEquivalent().offset()).location().x()) {
452         ASSERT(mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag));
453         removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node());
454         m_endingPosition = startOfParagraphToMove.deepEquivalent();
455         return;
456     }
457     
458     moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination);
459     // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
460     m_endingPosition = endingSelection().start();
461 }
462
463 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(Node *insertedPlaceholder)
464 {
465     // Compute the difference between the style before the delete and the style now
466     // after the delete has been done. Set this style on the frame, so other editing
467     // commands being composed with this one will work, and also cache it on the command,
468     // so the Frame::appliedEditing can set it after the whole composite command 
469     // has completed.
470     // FIXME: Improve typing style.
471     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
472     
473     // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
474     if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.node()))
475         m_typingStyle = m_deleteIntoBlockquoteStyle;
476     m_deleteIntoBlockquoteStyle = 0;
477     
478     RefPtr<CSSComputedStyleDeclaration> endingStyle = new CSSComputedStyleDeclaration(m_endingPosition.node());
479     endingStyle->diff(m_typingStyle.get());
480     if (!m_typingStyle->length())
481         m_typingStyle = 0;
482     if (insertedPlaceholder && m_typingStyle) {
483         // Apply style to the placeholder. This makes sure that the single line in the
484         // paragraph has the right height, and that the paragraph takes on the style
485         // of the preceding line and retains it even if you click away, click back, and
486         // then start typing. In this case, the typing style is applied right now, and
487         // is not retained until the next typing action.
488
489         setEndingSelection(Selection(Position(insertedPlaceholder, 0), DOWNSTREAM));
490         applyStyle(m_typingStyle.get(), EditActionUnspecified);
491         m_typingStyle = 0;
492     }
493     // Set m_typingStyle as the typing style.
494     // It's perfectly OK for m_typingStyle to be null.
495     document()->frame()->setTypingStyle(m_typingStyle.get());
496     setTypingStyle(m_typingStyle.get());
497 }
498
499 void DeleteSelectionCommand::clearTransientState()
500 {
501     m_selectionToDelete = Selection();
502     m_upstreamStart.clear();
503     m_downstreamStart.clear();
504     m_upstreamEnd.clear();
505     m_downstreamEnd.clear();
506     m_endingPosition.clear();
507     m_leadingWhitespace.clear();
508     m_trailingWhitespace.clear();
509 }
510
511 void DeleteSelectionCommand::doApply()
512 {
513     // If selection has not been set to a custom selection when the command was created,
514     // use the current ending selection.
515     if (!m_hasSelectionToDelete)
516         m_selectionToDelete = endingSelection();
517     
518     if (!m_selectionToDelete.isRange())
519         return;
520
521     // 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. 
522     if (!m_replace) {
523         Node* startNode = m_selectionToDelete.start().node();
524         Node* ancestorNode = startNode ? startNode->shadowAncestorNode() : 0;
525         if (ancestorNode && ancestorNode->hasTagName(inputTag) && static_cast<HTMLInputElement*>(ancestorNode)->isNonWidgetTextField())
526             document()->frame()->textWillBeDeletedInTextField(static_cast<Element*>(ancestorNode));
527     }
528
529     // save this to later make the selection with
530     EAffinity affinity = m_selectionToDelete.affinity();
531     
532     Position downstreamEnd = m_selectionToDelete.end().downstream();
533     m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart()) &&
534                         isEndOfParagraph(m_selectionToDelete.visibleEnd()) &&
535                         !(downstreamEnd.node()->hasTagName(brTag) && downstreamEnd.offset() == 0) &&
536                         !(downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->style()->preserveNewline() && m_selectionToDelete.visibleEnd().characterAfter() == '\n');
537     
538     // set up our state
539     initializePositionData();
540     if (!m_startBlock || !m_endBlock) {
541         // Can't figure out what blocks we're in. This can happen if
542         // the document structure is not what we are expecting, like if
543         // the document has no body element, or if the editable block
544         // has been changed to display: inline. Some day it might
545         // be nice to be able to deal with this, but for now, bail.
546         clearTransientState();
547         return;
548     }
549
550     // Delete any text that may hinder our ability to fixup whitespace after the delete
551     deleteInsignificantTextDownstream(m_trailingWhitespace);    
552
553     saveTypingStyleState();
554     
555     // deleting just a BR is handled specially, at least because we do not
556     // want to replace it with a placeholder BR!
557     if (handleSpecialCaseBRDelete()) {
558         calculateTypingStyleAfterDelete(false);
559         setEndingSelection(Selection(m_endingPosition, affinity));
560         clearTransientState();
561         rebalanceWhitespace();
562         return;
563     }
564     
565     handleGeneralDelete();
566     
567     fixupWhitespace();
568
569     RefPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()) : 0;
570     
571     mergeParagraphs();
572     
573     if (placeholder)
574         insertNodeAt(placeholder.get(), m_endingPosition.node(), m_endingPosition.offset());
575
576     calculateTypingStyleAfterDelete(placeholder.get());
577     
578     rebalanceWhitespaceAt(m_endingPosition);
579     setEndingSelection(Selection(m_endingPosition, affinity));
580     clearTransientState();
581 }
582
583 EditAction DeleteSelectionCommand::editingAction() const
584 {
585     // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
586     // but in that case there's a TypingCommand that supplies the editingAction(), so
587     // the Undo menu correctly shows "Undo Typing"
588     return EditActionCut;
589 }
590
591 bool DeleteSelectionCommand::preservesTypingStyle() const
592 {
593     return true;
594 }
595
596 } // namespace WebCore