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