Use is<>() / downcast<>() for Table render objects
[WebKit-https.git] / Source / WebCore / editing / DeleteSelectionCommand.cpp
index 3ba5ae9..a107e69 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
+ * Copyright (C) 2005 Apple Inc.  All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 #include "DeleteSelectionCommand.h"
 
 #include "Document.h"
-#include "DocumentFragment.h"
-#include "EditingBoundary.h"
+#include "DocumentMarkerController.h"
 #include "Editor.h"
 #include "EditorClient.h"
 #include "Element.h"
 #include "Frame.h"
 #include "htmlediting.h"
 #include "HTMLInputElement.h"
+#include "HTMLLinkElement.h"
 #include "HTMLNames.h"
+#include "HTMLStyleElement.h"
+#include "HTMLTableElement.h"
+#include "NodeTraversal.h"
 #include "RenderTableCell.h"
+#include "RenderText.h"
 #include "Text.h"
-#include "visible_units.h"
+#include "VisibleUnits.h"
 
 namespace WebCore {
 
@@ -52,7 +56,7 @@ static bool isTableRow(const Node* node)
 static bool isTableCellEmpty(Node* cell)
 {
     ASSERT(isTableCell(cell));
-    return VisiblePosition(firstDeepEditingPositionForNode(cell)) == VisiblePosition(lastDeepEditingPositionForNode(cell));
+    return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPositionInNode(cell));
 }
 
 static bool isTableRowEmpty(Node* row)
@@ -67,36 +71,40 @@ static bool isTableRowEmpty(Node* row)
     return true;
 }
 
-DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
-    : CompositeEditCommand(document), 
-      m_hasSelectionToDelete(false), 
-      m_smartDelete(smartDelete), 
-      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
-      m_replace(replace),
-      m_expandForSpecialElements(expandForSpecialElements),
-      m_pruneStartBlockIfNecessary(false),
-      m_startsAtEmptyLine(false),
-      m_startBlock(0),
-      m_endBlock(0),
-      m_typingStyle(0),
-      m_deleteIntoBlockquoteStyle(0)
+DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
+    : CompositeEditCommand(document)
+    , m_hasSelectionToDelete(false)
+    , m_smartDelete(smartDelete)
+    , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
+    , m_needPlaceholder(false)
+    , m_replace(replace)
+    , m_expandForSpecialElements(expandForSpecialElements)
+    , m_pruneStartBlockIfNecessary(false)
+    , m_startsAtEmptyLine(false)
+    , m_sanitizeMarkup(sanitizeMarkup)
+    , m_startBlock(0)
+    , m_endBlock(0)
+    , m_typingStyle(0)
+    , m_deleteIntoBlockquoteStyle(0)
 {
 }
 
-DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
-    : CompositeEditCommand(selection.start().node()->document()), 
-      m_hasSelectionToDelete(true), 
-      m_smartDelete(smartDelete), 
-      m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
-      m_replace(replace),
-      m_expandForSpecialElements(expandForSpecialElements),
-      m_pruneStartBlockIfNecessary(false),
-      m_startsAtEmptyLine(false),
-      m_selectionToDelete(selection),
-      m_startBlock(0),
-      m_endBlock(0),
-      m_typingStyle(0),
-      m_deleteIntoBlockquoteStyle(0)
+DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
+    : CompositeEditCommand(selection.start().anchorNode()->document())
+    , m_hasSelectionToDelete(true)
+    , m_smartDelete(smartDelete)
+    , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
+    , m_needPlaceholder(false)
+    , m_replace(replace)
+    , m_expandForSpecialElements(expandForSpecialElements)
+    , m_pruneStartBlockIfNecessary(false)
+    , m_startsAtEmptyLine(false)
+    , m_sanitizeMarkup(sanitizeMarkup)
+    , m_selectionToDelete(selection)
+    , m_startBlock(0)
+    , m_endBlock(0)
+    , m_typingStyle(0)
+    , m_deleteIntoBlockquoteStyle(0)
 {
 }
 
@@ -110,10 +118,10 @@ void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end)
  
     // 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,
     // but in these cases, we want to delete it, so manually expand the selection
-    if (start.node()->hasTagName(hrTag))
-        start = positionBeforeNode(start.node());
-    else if (end.node()->hasTagName(hrTag))
-        end = positionAfterNode(end.node());
+    if (start.deprecatedNode()->hasTagName(hrTag))
+        start = positionBeforeNode(start.deprecatedNode());
+    else if (end.deprecatedNode()->hasTagName(hrTag))
+        end = positionAfterNode(end.deprecatedNode());
     
     // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expansion.
     if (!m_expandForSpecialElements)
@@ -128,7 +136,9 @@ void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end)
         
         if (!startSpecialContainer && !endSpecialContainer)
             break;
-            
+
+        m_mergeBlocksAfterDelete = false;
+
         if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || VisiblePosition(end) != m_selectionToDelete.visibleEnd())
             break;
 
@@ -166,7 +176,7 @@ void DeleteSelectionCommand::setStartingSelectionOnSmartDelete(const Position& s
         newBase = end;
         newExtent = start;        
     }
-    setStartingSelection(VisibleSelection(newBase, newExtent));            
+    setStartingSelection(VisibleSelection(newBase, newExtent, startingSelection().isDirectional())); 
 }
     
 void DeleteSelectionCommand::initializePositionData()
@@ -188,8 +198,8 @@ void DeleteSelectionCommand::initializePositionData()
     // Don't move content out of a table cell.
     // If the cell is non-editable, enclosingNodeOfType won't return it by default, so
     // tell that function that we don't care if it returns non-editable nodes.
-    Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, false);
-    Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, false);
+    Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary);
+    Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary);
     // FIXME: This isn't right.  A borderless table with two rows and a single column would appear as two paragraphs.
     if (endCell && endCell != startCell)
         m_mergeBlocksAfterDelete = false;
@@ -262,8 +272,8 @@ void DeleteSelectionCommand::initializePositionData()
     // like the one below, since editing functions should obviously accept editing positions.
     // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable
     // node.  This was done to match existing behavior, but it seems wrong.
-    m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, false);
-    m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, false);
+    m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
+    m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
 }
 
 void DeleteSelectionCommand::saveTypingStyleState()
@@ -275,16 +285,20 @@ void DeleteSelectionCommand::saveTypingStyleState()
     // typing style at the start of the selection, nor is there a reason to 
     // compute the style at the start of the selection after deletion (see the 
     // early return in calculateTypingStyleAfterDelete).
-    if (m_upstreamStart.node() == m_downstreamEnd.node() && m_upstreamStart.node()->isTextNode())
+    // However, if typing style was previously set from another text node at the previous
+    // position (now deleted), we need to clear that style as well.
+    if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() && m_upstreamStart.deprecatedNode()->isTextNode()) {
+        frame().selection().clearTypingStyle();
         return;
+    }
 
     // Figure out the typing style in effect before the delete is done.
-    m_typingStyle = EditingStyle::create(positionBeforeTabSpan(m_selectionToDelete.start()));
+    m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), EditingStyle::EditingPropertiesInEffect);
     m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDelete.start()));
 
     // If we're deleting into a Mail blockquote, save the style at end() instead of start()
     // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
-    if (nearestMailBlockquote(m_selectionToDelete.start().node()))
+    if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote))
         m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end());
     else
         m_deleteIntoBlockquoteStyle = 0;
@@ -292,18 +306,31 @@ void DeleteSelectionCommand::saveTypingStyleState()
 
 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
 {
+    Node* nodeAfterUpstreamStart = m_upstreamStart.computeNodeAfterPosition();
+    Node* nodeAfterDownstreamStart = m_downstreamStart.computeNodeAfterPosition();
+    // Upstream end will appear before BR due to canonicalization
+    Node* nodeAfterUpstreamEnd = m_upstreamEnd.computeNodeAfterPosition();
+
+    if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart)
+        return false;
+
     // Check for special-case where the selection contains only a BR on a line by itself after another BR.
-    bool upstreamStartIsBR = m_upstreamStart.node()->hasTagName(brTag);
-    bool downstreamStartIsBR = m_downstreamStart.node()->hasTagName(brTag);
-    bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
+    bool upstreamStartIsBR = nodeAfterUpstreamStart->hasTagName(brTag);
+    bool downstreamStartIsBR = nodeAfterDownstreamStart->hasTagName(brTag);
+    // We should consider that the BR is on a line by itself also when we have <br><br>. This test should be true only
+    // when the two elements are siblings and should be false in a case like <div><br></div><br>.
+    bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && ((nodeAfterDownstreamStart == nodeAfterUpstreamEnd) || (nodeAfterUpstreamEnd && nodeAfterUpstreamEnd->hasTagName(brTag) && nodeAfterUpstreamStart->nextSibling() == nodeAfterUpstreamEnd));
+
     if (isBROnLineByItself) {
-        removeNode(m_downstreamStart.node());
+        removeNode(nodeAfterDownstreamStart);
         return true;
     }
 
-    // Not a special-case delete per se, but we can detect that the merging of content between blocks
-    // should not be done.
-    if (upstreamStartIsBR && downstreamStartIsBR) {
+    // FIXME: This code doesn't belong in here.
+    // We detect the case where the start is an empty line consisting of BR not wrapped in a block element.
+    if (upstreamStartIsBR && downstreamStartIsBR
+        && !(isStartOfBlock(positionBeforeNode(nodeAfterUpstreamStart)) && isEndOfBlock(positionAfterNode(nodeAfterDownstreamStart)))
+        && (!nodeAfterUpstreamEnd || nodeAfterUpstreamEnd->hasTagName(brTag) || nodeAfterUpstreamEnd->previousSibling() != nodeAfterUpstreamStart)) {
         m_startsAtEmptyLine = true;
         m_endingPosition = m_downstreamEnd;
     }
@@ -311,36 +338,23 @@ bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
     return false;
 }
 
-static void updatePositionForNodeRemoval(Node* node, Position& position)
+static Position firstEditablePositionInNode(Node* node)
 {
-    if (position.isNull())
-        return;
-    switch (position.anchorType()) {
-    case Position::PositionIsOffsetInAnchor:
-        if (position.containerNode() == node->parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node->nodeIndex())
-            position.moveToOffset(position.offsetInContainerNode() - 1);
-        else if (node->contains(position.containerNode()))
-            position = positionInParentBeforeNode(node);
-        break;
-    case Position::PositionIsAfterAnchor:
-        if (node->contains(position.anchorNode()))
-            position = positionInParentAfterNode(node);
-        break;
-    case Position::PositionIsBeforeAnchor:
-        if (node->contains(position.anchorNode()))
-            position = positionInParentBeforeNode(node);
-        break;
-    }
+    ASSERT(node);
+    Node* next = node;
+    while (next && !next->hasEditableStyle())
+        next = NodeTraversal::next(next, node);
+    return next ? firstPositionInOrBeforeNode(next) : Position();
 }
 
-void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
+void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
 {
     if (!node)
         return;
         
     if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.get()) && node->isDescendantOf(m_endRoot.get()))) {
         // If a node is not in both the start and end editable roots, remove it only if its inside an editable region.
-        if (!node->parentNode()->isContentEditable()) {
+        if (!node->parentNode()->hasEditableStyle()) {
             // Don't remove non-editable atomic nodes.
             if (!node->firstChild())
                 return;
@@ -348,7 +362,7 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
             RefPtr<Node> child = node->firstChild();
             while (child) {
                 RefPtr<Node> nextChild = child->nextSibling();
-                removeNode(child.get());
+                removeNode(child.get(), shouldAssumeContentIsAlwaysEditable);
                 // Bail if nextChild is no longer node's child.
                 if (nextChild && nextChild->parentNode() != node)
                     return;
@@ -360,35 +374,38 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
         }
     }
     
-    if (isTableStructureNode(node.get()) || node == node->rootEditableElement()) {
+    if (isTableStructureNode(node.get()) || node->isRootEditableElement()) {
         // Do not remove an element of table structure; remove its contents.
         // Likewise for the root editable element.
         Node* child = node->firstChild();
         while (child) {
             Node* remove = child;
             child = child->nextSibling();
-            removeNode(remove);
+            removeNode(remove, shouldAssumeContentIsAlwaysEditable);
         }
         
-        // make sure empty cell has some height
-        updateLayout();
-        RenderObject *r = node->renderer();
-        if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0)
-            insertBlockPlaceholder(firstPositionInNode(node.get()));
+        // Make sure empty cell has some height, if a placeholder can be inserted.
+        document().updateLayoutIgnorePendingStylesheets();
+        RenderObject* renderer = node->renderer();
+        if (is<RenderTableCell>(renderer) && downcast<RenderTableCell>(*renderer).contentHeight() <= 0) {
+            Position firstEditablePosition = firstEditablePositionInNode(node.get());
+            if (firstEditablePosition.isNotNull())
+                insertBlockPlaceholder(firstEditablePosition);
+        }
         return;
     }
     
-    if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstDeepEditingPositionForNode(m_startBlock.get())).previous()))
+    if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNode(m_startBlock.get())).previous()))
         m_needPlaceholder = true;
-    else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastDeepEditingPositionForNode(m_startBlock.get())).next()))
+    else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionInNode(m_startBlock.get())).next()))
         m_needPlaceholder = true;
     
     // FIXME: Update the endpoints of the range being deleted.
-    updatePositionForNodeRemoval(node.get(), m_endingPosition);
-    updatePositionForNodeRemoval(node.get(), m_leadingWhitespace);
-    updatePositionForNodeRemoval(node.get(), m_trailingWhitespace);
+    updatePositionForNodeRemoval(m_endingPosition, node.get());
+    updatePositionForNodeRemoval(m_leadingWhitespace, node.get());
+    updatePositionForNodeRemoval(m_trailingWhitespace, node.get());
     
-    CompositeEditCommand::removeNode(node);
+    CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable);
 }
 
 static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
@@ -413,25 +430,50 @@ void DeleteSelectionCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned
     CompositeEditCommand::deleteTextFromNode(node, offset, count);
 }
 
+void DeleteSelectionCommand::makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss()
+{
+    RefPtr<Range> range = m_selectionToDelete.toNormalizedRange();
+    RefPtr<Node> node = range->firstNode();
+    while (node && node != range->pastLastNode()) {
+        RefPtr<Node> nextNode = NodeTraversal::next(node.get());
+        if ((is<HTMLStyleElement>(*node) && !downcast<HTMLStyleElement>(*node).hasAttribute(scopedAttr)) || is<HTMLLinkElement>(*node)) {
+            nextNode = NodeTraversal::nextSkippingChildren(node.get());
+            RefPtr<ContainerNode> rootEditableElement = node->rootEditableElement();
+            if (rootEditableElement) {
+                removeNode(node);
+                appendNode(node, rootEditableElement);
+            }
+        }
+        node = nextNode;
+    }
+}
+
 void DeleteSelectionCommand::handleGeneralDelete()
 {
+    if (m_upstreamStart.isNull())
+        return;
+
     int startOffset = m_upstreamStart.deprecatedEditingOffset();
-    Node* startNode = m_upstreamStart.node();
+    Node* startNode = m_upstreamStart.deprecatedNode();
     
+    makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss();
+
     // Never remove the start block unless it's a table, in which case we won't merge content in.
-    if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditing(startNode) && !startNode->hasTagName(tableTag)) {
+    if (startNode == m_startBlock && !startOffset && canHaveChildrenForEditing(startNode) && !is<HTMLTableElement>(*startNode)) {
         startOffset = 0;
-        startNode = startNode->traverseNextNode();
+        startNode = NodeTraversal::next(startNode);
+        if (!startNode)
+            return;
     }
 
-    if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) {
-        Text *text = static_cast<Text *>(startNode);
-        if (text->length() > (unsigned)caretMaxOffset(startNode))
-            deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode));
+    if (startOffset >= caretMaxOffset(startNode) && is<Text>(*startNode)) {
+        Text& text = downcast<Text>(*startNode);
+        if (text.length() > static_cast<unsigned>(caretMaxOffset(startNode)))
+            deleteTextFromNode(&text, caretMaxOffset(startNode), text.length() - caretMaxOffset(startNode));
     }
 
     if (startOffset >= lastOffsetForEditing(startNode)) {
-        startNode = startNode->traverseNextSibling();
+        startNode = NodeTraversal::nextSkippingChildren(startNode);
         startOffset = 0;
     }
 
@@ -439,12 +481,12 @@ void DeleteSelectionCommand::handleGeneralDelete()
     if (!startNode)
         return;
 
-    if (startNode == m_downstreamEnd.node()) {
+    if (startNode == m_downstreamEnd.deprecatedNode()) {
         if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) {
-            if (startNode->isTextNode()) {
+            if (is<Text>(*startNode)) {
                 // in a text node that needs to be trimmed
-                Text* text = static_cast<Text*>(startNode);
-                deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset);
+                Text& text = downcast<Text>(*startNode);
+                deleteTextFromNode(&text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset);
             } else {
                 removeChildrenInRange(startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset());
                 m_endingPosition = m_upstreamStart;
@@ -456,78 +498,74 @@ void DeleteSelectionCommand::handleGeneralDelete()
             removeNode(startNode);
     }
     else {
-        bool startNodeWasDescendantOfEndNode = m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node());
+        bool startNodeWasDescendantOfEndNode = m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode());
         // The selection to delete spans more than one node.
         RefPtr<Node> node(startNode);
         
         if (startOffset > 0) {
-            if (startNode->isTextNode()) {
+            if (is<Text>(*startNode)) {
                 // in a text node that needs to be trimmed
-                Text *text = static_cast<Text *>(node.get());
-                deleteTextFromNode(text, startOffset, text->length() - startOffset);
-                node = node->traverseNextNode();
+                Text& text = downcast<Text>(*node);
+                deleteTextFromNode(&text, startOffset, text.length() - startOffset);
+                node = NodeTraversal::next(node.get());
             } else {
-                node = startNode->childNode(startOffset);
+                node = startNode->traverseToChildAt(startOffset);
             }
-        } else if (startNode == m_upstreamEnd.node() && startNode->isTextNode()) {
-            Text* text = static_cast<Text*>(m_upstreamEnd.node());
-            deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset());
+        } else if (startNode == m_upstreamEnd.deprecatedNode() && is<Text>(*startNode)) {
+            Text& text = downcast<Text>(*m_upstreamEnd.deprecatedNode());
+            deleteTextFromNode(&text, 0, m_upstreamEnd.deprecatedEditingOffset());
         }
         
         // handle deleting all nodes that are completely selected
-        while (node && node != m_downstreamEnd.node()) {
+        while (node && node != m_downstreamEnd.deprecatedNode()) {
             if (comparePositions(firstPositionInOrBeforeNode(node.get()), m_downstreamEnd) >= 0) {
-                // traverseNextSibling just blew past the end position, so stop deleting
+                // NodeTraversal::nextSkippingChildren just blew past the end position, so stop deleting
                 node = 0;
-            } else if (!m_downstreamEnd.node()->isDescendantOf(node.get())) {
-                RefPtr<Node> nextNode = node->traverseNextSibling();
+            } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.get())) {
+                RefPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(node.get());
                 // if we just removed a node from the end container, update end position so the
                 // check above will work
-                if (node->parentNode() == m_downstreamEnd.node()) {
-                    ASSERT(m_downstreamEnd.deprecatedEditingOffset());
-                    ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.deprecatedEditingOffset());
-                    m_downstreamEnd.moveToOffset(m_downstreamEnd.deprecatedEditingOffset() - 1);
-                }
+                updatePositionForNodeRemoval(m_downstreamEnd, node.get());
                 removeNode(node.get());
                 node = nextNode.get();
             } else {
                 Node* n = node->lastDescendant();
-                if (m_downstreamEnd.node() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(n)) {
+                if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(n)) {
                     removeNode(node.get());
                     node = 0;
                 } else
-                    node = node->traverseNextNode();
+                    node = NodeTraversal::next(node.get());
             }
         }
         
-        if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.node())) {
-            if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.node())) {
+        if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()) && m_downstreamEnd.anchorNode()->inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode())) {
+            if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.deprecatedNode())) {
                 // The node itself is fully selected, not just its contents.  Delete it.
-                removeNode(m_downstreamEnd.node());
+                removeNode(m_downstreamEnd.deprecatedNode());
             } else {
-                if (m_downstreamEnd.node()->isTextNode()) {
+                if (is<Text>(*m_downstreamEnd.deprecatedNode())) {
                     // in a text node that needs to be trimmed
-                    Text *text = static_cast<Text *>(m_downstreamEnd.node());
+                    Text& text = downcast<Text>(*m_downstreamEnd.deprecatedNode());
                     if (m_downstreamEnd.deprecatedEditingOffset() > 0) {
-                        deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset());
+                        deleteTextFromNode(&text, 0, m_downstreamEnd.deprecatedEditingOffset());
                     }
-                // Remove children of m_downstreamEnd.node() that come after m_upstreamStart.
-                // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.node()
+                // Remove children of m_downstreamEnd.deprecatedNode() that come after m_upstreamStart.
+                // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.deprecatedNode()
                 // and m_upstreamStart has been removed from the document, because then we don't 
                 // know how many children to remove.
                 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can
                 // always know which children to remove.
-                } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.node()->inDocument())) {
-                    int offset = 0;
-                    if (m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node())) {
-                        Node *n = m_upstreamStart.node();
-                        while (n && n->parentNode() != m_downstreamEnd.node())
+                } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.anchorNode()->inDocument())) {
+                    unsigned offset = 0;
+                    if (m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())) {
+                        Node* n = m_upstreamStart.deprecatedNode();
+                        while (n && n->parentNode() != m_downstreamEnd.deprecatedNode())
                             n = n->parentNode();
                         if (n)
-                            offset = n->nodeIndex() + 1;
+                            offset = n->computeNodeIndex() + 1;
                     }
-                    removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.deprecatedEditingOffset());
-                    m_downstreamEnd.moveToOffset(offset);
+                    removeChildrenInRange(m_downstreamEnd.deprecatedNode(), offset, m_downstreamEnd.deprecatedEditingOffset());
+                    m_downstreamEnd = createLegacyEditingPosition(m_downstreamEnd.deprecatedNode(), offset);
                 }
             }
         }
@@ -536,17 +574,17 @@ void DeleteSelectionCommand::handleGeneralDelete()
 
 void DeleteSelectionCommand::fixupWhitespace()
 {
-    updateLayout();
+    document().updateLayoutIgnorePendingStylesheets();
     // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
-    if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && m_leadingWhitespace.node()->isTextNode()) {
-        Text* textNode = static_cast<Text*>(m_leadingWhitespace.node());
-        ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
-        replaceTextInNode(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
+    if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && is<Text>(*m_leadingWhitespace.deprecatedNode())) {
+        Text& textNode = downcast<Text>(*m_leadingWhitespace.deprecatedNode());
+        ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace());
+        replaceTextInNodePreservingMarkers(&textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
     }
-    if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && m_trailingWhitespace.node()->isTextNode()) {
-        Text* textNode = static_cast<Text*>(m_trailingWhitespace.node());
-        ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
-        replaceTextInNode(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
+    if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && is<Text>(*m_trailingWhitespace.deprecatedNode())) {
+        Text& textNode = downcast<Text>(*m_trailingWhitespace.deprecatedNode());
+        ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace());
+        replaceTextInNodePreservingMarkers(&textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
     }
 }
 
@@ -569,7 +607,7 @@ void DeleteSelectionCommand::mergeParagraphs()
     ASSERT(!m_pruneStartBlockIfNecessary);
 
     // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
-    if (!m_downstreamEnd.node()->inDocument() || !m_upstreamStart.node()->inDocument())
+    if (!m_downstreamEnd.anchorNode()->inDocument() || !m_upstreamStart.anchorNode()->inDocument())
          return;
          
     // FIXME: The deletion algorithm shouldn't let this happen.
@@ -585,14 +623,14 @@ void DeleteSelectionCommand::mergeParagraphs()
     
     // m_downstreamEnd's block has been emptied out by deletion.  There is no content inside of it to
     // move, so just remove it.
-    Element* endBlock = static_cast<Element*>(enclosingBlock(m_downstreamEnd.node()));
-    if (!startOfParagraphToMove.deepEquivalent().node() || !endBlock->contains(startOfParagraphToMove.deepEquivalent().node())) {
-        removeNode(enclosingBlock(m_downstreamEnd.node()));
+    Element* endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode());
+    if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) {
+        removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode()));
         return;
     }
     
     // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
-    if (!mergeDestination.deepEquivalent().node() || !mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement()) || m_startsAtEmptyLine) {
+    if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) || m_startsAtEmptyLine) {
         insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
         mergeDestination = VisiblePosition(m_upstreamStart);
     }
@@ -600,7 +638,7 @@ void DeleteSelectionCommand::mergeParagraphs()
     if (mergeDestination == startOfParagraphToMove)
         return;
         
-    VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove);
+    VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove, CanSkipOverEditingBoundary);
     
     if (mergeDestination == endOfParagraphToMove)
         return;
@@ -608,8 +646,8 @@ void DeleteSelectionCommand::mergeParagraphs()
     // The rule for merging into an empty block is: only do so if its farther to the right.
     // FIXME: Consider RTL.
     if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
-        if (mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag)) {
-            removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node());
+        if (mergeDestination.deepEquivalent().downstream().deprecatedNode()->hasTagName(brTag)) {
+            removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().deprecatedNode());
             m_endingPosition = startOfParagraphToMove.deepEquivalent();
             return;
         }
@@ -618,14 +656,14 @@ void DeleteSelectionCommand::mergeParagraphs()
     // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination.  If there is 
     // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the caret to just before the selection we deleted.
     // See https://bugs.webkit.org/show_bug.cgi?id=25439
-    if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().node()) && !isStartOfParagraph(mergeDestination)) {
+    if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) {
         m_endingPosition = m_upstreamStart;
         return;
     }
     
     RefPtr<Range> range = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), endOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent());
     RefPtr<Range> rangeToBeReplaced = Range::create(document(), mergeDestination.deepEquivalent().parentAnchoredEquivalent(), mergeDestination.deepEquivalent().parentAnchoredEquivalent());
-    if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(range.get(), rangeToBeReplaced.get()))
+    if (!frame().editor().client()->shouldMoveRangeAfterDelete(range.get(), rangeToBeReplaced.get()))
         return;
     
     // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block
@@ -666,7 +704,7 @@ void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows()
     if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow)
         if (isTableRowEmpty(m_endTableRow.get())) {
             // Don't remove m_endTableRow if it's where we're putting the ending selection.
-            if (!m_endingPosition.node()->isDescendantOf(m_endTableRow.get())) {
+            if (!m_endingPosition.deprecatedNode()->isDescendantOf(m_endTableRow.get())) {
                 // FIXME: We probably shouldn't remove m_endTableRow unless it's fully selected, even if it is empty.
                 // We'll need to start adjusting the selection endpoints during deletion to know whether or not m_endTableRow
                 // was fully selected here.
@@ -687,35 +725,18 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
     // has completed.
     
     // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
-    if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.node()))
+    if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary))
         m_typingStyle = m_deleteIntoBlockquoteStyle;
     m_deleteIntoBlockquoteStyle = 0;
 
     m_typingStyle->prepareToApplyAt(m_endingPosition);
     if (m_typingStyle->isEmpty())
         m_typingStyle = 0;
-    VisiblePosition visibleEnd(m_endingPosition);
-    if (m_typingStyle && 
-        isStartOfParagraph(visibleEnd) &&
-        isEndOfParagraph(visibleEnd) &&
-        lineBreakExistsAtVisiblePosition(visibleEnd)) {
-        // Apply style to the placeholder that is now holding open the empty paragraph. 
-        // This makes sure that the paragraph has the right height, and that the paragraph 
-        // takes on the right style and retains it even if you move the selection away and
-        // then move it back (which will clear typing style).
-
-        setEndingSelection(visibleEnd);
-        applyStyle(m_typingStyle.get(), EditActionUnspecified);
-        // applyStyle can destroy the placeholder that was at m_endingPosition if it needs to 
-        // move it, but it will set an endingSelection() at [movedPlaceholder, 0] if it does so.
-        m_endingPosition = endingSelection().start();
-        m_typingStyle = 0;
-    }
     // This is where we've deleted all traces of a style but not a whole paragraph (that's handled above).
     // In this case if we start typing, the new characters should have the same style as the just deleted ones,
     // but, if we change the selection, come back and start typing that style should be lost.  Also see 
     // preserveTypingStyle() below.
-    document()->frame()->selection()->setTypingStyle(m_typingStyle);
+    frame().selection().setTypingStyle(m_typingStyle);
 }
 
 void DeleteSelectionCommand::clearTransientState()
@@ -729,6 +750,48 @@ void DeleteSelectionCommand::clearTransientState()
     m_leadingWhitespace.clear();
     m_trailingWhitespace.clear();
 }
+    
+String DeleteSelectionCommand::originalStringForAutocorrectionAtBeginningOfSelection()
+{
+    if (!m_selectionToDelete.isRange())
+        return String();
+
+    VisiblePosition startOfSelection = m_selectionToDelete.start();
+    if (!isStartOfWord(startOfSelection))
+        return String();
+
+    VisiblePosition nextPosition = startOfSelection.next();
+    if (nextPosition.isNull())
+        return String();
+
+    RefPtr<Range> rangeOfFirstCharacter = Range::create(document(), startOfSelection.deepEquivalent(), nextPosition.deepEquivalent());
+    Vector<DocumentMarker*> markers = document().markers().markersInRange(rangeOfFirstCharacter.get(), DocumentMarker::Autocorrected);
+    for (size_t i = 0; i < markers.size(); ++i) {
+        const DocumentMarker* marker = markers[i];
+        int startOffset = marker->startOffset();
+        if (startOffset == startOfSelection.deepEquivalent().offsetInContainerNode())
+            return marker->description();
+    }
+    return String();
+}
+
+// This method removes div elements with no attributes that have only one child or no children at all.
+void DeleteSelectionCommand::removeRedundantBlocks()
+{
+    Node* node = m_endingPosition.containerNode();
+    Node* rootNode = node->rootEditableElement();
+   
+    while (node != rootNode) {
+        if (isRemovableBlock(node)) {
+            if (node == m_endingPosition.anchorNode())
+                updatePositionForNodeRemovalPreservingChildren(m_endingPosition, node);
+            
+            CompositeEditCommand::removeNodePreservingChildren(node);
+            node = m_endingPosition.anchorNode();
+        } else
+            node = node->parentNode();
+    }
+}
 
 void DeleteSelectionCommand::doApply()
 {
@@ -740,14 +803,13 @@ void DeleteSelectionCommand::doApply()
     if (!m_selectionToDelete.isNonOrphanedRange())
         return;
 
+    String originalString = originalStringForAutocorrectionAtBeginningOfSelection();
+
     // 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. 
     if (!m_replace) {
-        Node* startNode = m_selectionToDelete.start().node();
-        Node* ancestorNode = startNode ? startNode->shadowAncestorNode() : 0;
-        if (ancestorNode && ancestorNode->hasTagName(inputTag)
-                && static_cast<HTMLInputElement*>(ancestorNode)->isTextField()
-                && ancestorNode->focused())
-            document()->frame()->editor()->textWillBeDeletedInTextField(static_cast<Element*>(ancestorNode));
+        Element* textControl = enclosingTextFormControl(m_selectionToDelete.start());
+        if (textControl && textControl->focused())
+            frame().editor().textWillBeDeletedInTextField(textControl);
     }
 
     // save this to later make the selection with
@@ -762,7 +824,7 @@ void DeleteSelectionCommand::doApply()
         // and ends inside it (we do need placeholders to hold open empty cells, but that's
         // handled elsewhere).
         if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart()))
-            if (m_selectionToDelete.end().node()->isDescendantOf(table))
+            if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table))
                 m_needPlaceholder = false;
     }
         
@@ -779,7 +841,7 @@ void DeleteSelectionCommand::doApply()
     // want to replace it with a placeholder BR!
     if (handleSpecialCaseBRDelete()) {
         calculateTypingStyleAfterDelete();
-        setEndingSelection(VisibleSelection(m_endingPosition, affinity));
+        setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
         clearTransientState();
         rebalanceWhitespace();
         return;
@@ -795,14 +857,30 @@ void DeleteSelectionCommand::doApply()
     
     RefPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()).get() : 0;
     
-    if (placeholder)
+    if (placeholder) {
+        if (m_sanitizeMarkup)
+            removeRedundantBlocks();
         insertNodeAt(placeholder.get(), m_endingPosition);
+    }
 
-    rebalanceWhitespaceAt(m_endingPosition);
+    bool shouldRebalaceWhiteSpace = true;
+    if (!frame().editor().behavior().shouldRebalanceWhiteSpacesInSecureField()) {
+        Node* node = m_endingPosition.deprecatedNode();
+        if (is<Text>(node)) {
+            Text& textNode = downcast<Text>(*node);
+            if (textNode.length())
+                shouldRebalaceWhiteSpace = textNode.renderer()->style().textSecurity() == TSNONE;
+        }        
+    }
+    if (shouldRebalaceWhiteSpace)
+        rebalanceWhitespaceAt(m_endingPosition);
 
     calculateTypingStyleAfterDelete();
-    
-    setEndingSelection(VisibleSelection(m_endingPosition, affinity));
+
+    if (!originalString.isEmpty())
+        frame().editor().deletedAutocorrectionAtPosition(m_endingPosition, originalString);
+
+    setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
     clearTransientState();
 }