Reviewed by Darin Adler.
[WebKit-https.git] / WebCore / editing / ReplaceSelectionCommand.cpp
index 781459b..05fe5a0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
+ * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 #include "ApplyStyleCommand.h"
 #include "BeforeTextInsertedEvent.h"
-#include "DocumentFragment.h"
+#include "CSSComputedStyleDeclaration.h"
+#include "CSSProperty.h"
+#include "CSSPropertyNames.h"
+#include "CSSValueKeywords.h"
 #include "Document.h"
+#include "DocumentFragment.h"
 #include "EditingText.h"
+#include "EventNames.h"
+#include "Element.h"
 #include "Frame.h"
 #include "HTMLElement.h"
-#include "VisiblePosition.h"
-#include "CSSComputedStyleDeclaration.h"
-#include "css_valueimpl.h"
-#include "CSSPropertyNames.h"
-#include "dom2_eventsimpl.h"
-#include "Range.h"
-#include "Position.h"
 #include "HTMLInterchange.h"
-#include "htmlediting.h"
+#include "HTMLInputElement.h"
 #include "HTMLNames.h"
-#include "markup.h"
-#include "RenderObject.h"
 #include "SelectionController.h"
-#include "visible_units.h"
+#include "SmartReplace.h"
 #include "TextIterator.h"
-#include <kxmlcore/Assertions.h>
+#include "htmlediting.h"
+#include "markup.h"
+#include "visible_units.h"
 
 namespace WebCore {
 
+using namespace EventNames;
 using namespace HTMLNames;
 
-ReplacementFragment::ReplacementFragment(Document *document, DocumentFragment *fragment, bool matchStyle, Element* editableRoot)
+enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
+
+// --- ReplacementFragment helper class
+
+class ReplacementFragment : Noncopyable {
+public:
+    ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const Selection&);
+
+    Node* firstChild() const;
+    Node* lastChild() const;
+
+    bool isEmpty() const;
+    
+    bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
+    bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
+    
+    void removeNode(PassRefPtr<Node>);
+    void removeNodePreservingChildren(Node*);
+
+private:
+    PassRefPtr<Node> insertFragmentForTestRendering(Node* context);
+    void removeUnrenderedNodes(Node*);
+    void restoreTestRenderingNodesToFragment(Node*);
+    void removeInterchangeNodes(Node*);
+    
+    void insertNodeBefore(Node* node, Node* refNode);
+
+    RefPtr<Document> m_document;
+    RefPtr<DocumentFragment> m_fragment;
+    bool m_matchStyle;
+    bool m_hasInterchangeNewlineAtStart;
+    bool m_hasInterchangeNewlineAtEnd;
+};
+
+static bool isInterchangeNewlineNode(const Node *node)
+{
+    static String interchangeNewlineClassString(AppleInterchangeNewline);
+    return node && node->hasTagName(brTag) && 
+           static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
+}
+
+static bool isInterchangeConvertedSpaceSpan(const Node *node)
+{
+    static String convertedSpaceSpanClassString(AppleConvertedSpace);
+    return node->isHTMLElement() && 
+           static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
+}
+
+ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
     : m_document(document),
       m_fragment(fragment),
       m_matchStyle(matchStyle), 
       m_hasInterchangeNewlineAtStart(false), 
-      m_hasInterchangeNewlineAtEnd(false), 
-      m_hasMoreThanOneBlock(false)
+      m_hasInterchangeNewlineAtEnd(false)
 {
     if (!m_document)
         return;
-
-    if (!m_fragment) {
-        m_type = EmptyFragment;
+    if (!m_fragment)
         return;
-    }
-
-    Node* firstChild = m_fragment->firstChild();
-    Node* lastChild = m_fragment->lastChild();
-
-    if (!firstChild) {
-        m_type = EmptyFragment;
+    if (!m_fragment->firstChild())
         return;
-    }
-    
-    m_type = firstChild == lastChild && firstChild->isTextNode() ? SingleTextNodeFragment : TreeFragment;
     
+    Element* editableRoot = selection.rootEditableElement();
     ASSERT(editableRoot);
     if (!editableRoot)
         return;
-            
-    RefPtr<Node> holder = insertFragmentForTestRendering();
+    
+    Node* shadowAncestorNode = editableRoot->shadowAncestorNode();
+    
+    if (!editableRoot->eventListenerForType(webkitBeforeTextInsertedEvent) &&
+        // FIXME: Remove these checks once textareas and textfields actually register an event handler.
+        !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextField()) &&
+        !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextArea()) &&
+        editableRoot->isContentRichlyEditable()) {
+        removeInterchangeNodes(m_fragment->firstChild());
+        return;
+    }
+
+    Node* styleNode = selection.base().node();
+    RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
     
     RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange();
     String text = plainText(range.get());
     // Give the root a chance to change the text.
-    RefPtr<BeforeTextInsertedEvent> evt = new BeforeTextInsertedEvent(text);
+    RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
     ExceptionCode ec = 0;
-    editableRoot->dispatchEvent(evt, ec, true);
+    editableRoot->dispatchEvent(evt, ec);
     ASSERT(ec == 0);
     if (text != evt->text() || !editableRoot->isContentRichlyEditable()) {
-        m_fragment = createFragmentFromText(document, evt->text().deprecatedString());
-        firstChild = m_fragment->firstChild();
-        lastChild = m_fragment->firstChild();
-        
+        restoreTestRenderingNodesToFragment(holder.get());
         removeNode(holder);
-        holder = insertFragmentForTestRendering();
+
+        m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
+        if (!m_fragment->firstChild())
+            return;
+        holder = insertFragmentForTestRendering(styleNode);
     }
     
-    Node *node = firstChild;
-    Node *newlineAtStartNode = 0;
-    Node *newlineAtEndNode = 0;
-    while (node) {
-        Node *next = node->traverseNextNode();
-        if (isInterchangeNewlineNode(node)) {
-            if (next || node == firstChild) {
-                m_hasInterchangeNewlineAtStart = true;
-                newlineAtStartNode = node;
-            }
-            else {
-                m_hasInterchangeNewlineAtEnd = true;
-                newlineAtEndNode = node;
-            }
-        }
-        else if (isInterchangeConvertedSpaceSpan(node)) {
-            RefPtr<Node> n = 0;
-            while ((n = node->firstChild())) {
-                removeNode(n);
-                insertNodeBefore(n.get(), node);
-            }
-            removeNode(node);
-            if (n)
-                next = n->traverseNextNode();
-        }
-        node = next;
-    }
-
-    if (newlineAtStartNode)
-        removeNode(newlineAtStartNode);
-    if (newlineAtEndNode)
-        removeNode(newlineAtEndNode);
+    removeInterchangeNodes(holder->firstChild());
     
-    saveRenderingInfo(holder.get());
     removeUnrenderedNodes(holder.get());
-    m_hasMoreThanOneBlock = renderedBlocks(holder.get()) > 1;
     restoreTestRenderingNodesToFragment(holder.get());
     removeNode(holder);
-    removeStyleNodes();
 }
 
-ReplacementFragment::~ReplacementFragment()
+bool ReplacementFragment::isEmpty() const
 {
+    return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
 }
 
 Node *ReplacementFragment::firstChild() const 
 { 
-    return m_fragment->firstChild()
+    return m_fragment ? m_fragment->firstChild() : 0
 }
 
 Node *ReplacementFragment::lastChild() const 
 { 
-    return m_fragment->lastChild(); 
-}
-
-static bool isMailPasteAsQuotationNode(const Node *node)
-{
-    return node && static_cast<const Element *>(node)->getAttribute("class") == ApplePasteAsQuotation;
-}
-
-Node *ReplacementFragment::mergeStartNode() const
-{
-    Node *node = m_fragment->firstChild();
-    while (node && isBlockFlow(node) && !isMailPasteAsQuotationNode(node))
-        node = node->traverseNextNode();
-    return node;
-}
-
-bool ReplacementFragment::isInterchangeNewlineNode(const Node *node)
-{
-    static String interchangeNewlineClassString(AppleInterchangeNewline);
-    return node && node->hasTagName(brTag) && 
-           static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
-}
-
-bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const Node *node)
-{
-    static String convertedSpaceSpanClassString(AppleConvertedSpace);
-    return node->isHTMLElement() && 
-           static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
-}
-
-Node *ReplacementFragment::enclosingBlock(Node *node) const
-{
-    while (node && !isBlockFlow(node))
-        node = node->parentNode();    
-    return node ? node : m_fragment.get();
+    return m_fragment ? m_fragment->lastChild() : 0; 
 }
 
 void ReplacementFragment::removeNodePreservingChildren(Node *node)
@@ -231,15 +214,30 @@ void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
     ASSERT(ec == 0);
 }
 
-PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering()
+PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context)
 {
-    Node *body = m_document->body();
+    Nodebody = m_document->body();
     if (!body)
         return 0;
 
-    RefPtr<Node> holder = createDefaultParagraphElement(m_document.get());
+    RefPtr<StyledElement> holder = static_pointer_cast<StyledElement>(createDefaultParagraphElement(m_document.get()));
     
     ExceptionCode ec = 0;
+
+    // Copy the whitespace and user-select style from the context onto this element.
+    // FIXME: We should examine other style properties to see if they would be appropriate to consider during the test rendering.
+    Node* n = context;
+    while (n && !n->isElementNode())
+        n = n->parentNode();
+    if (n) {
+        RefPtr<CSSComputedStyleDeclaration> conFontStyle = computedStyle(n);
+        CSSStyleDeclaration* style = holder->style();
+        style->setProperty(CSSPropertyWhiteSpace, conFontStyle->getPropertyValue(CSSPropertyWhiteSpace), false, ec);
+        ASSERT(ec == 0);
+        style->setProperty(CSSPropertyWebkitUserSelect, conFontStyle->getPropertyValue(CSSPropertyWebkitUserSelect), false, ec);
+        ASSERT(ec == 0);
+    }
+    
     holder->appendChild(m_fragment, ec);
     ASSERT(ec == 0);
     
@@ -265,596 +263,672 @@ void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder)
     }
 }
 
-bool ReplacementFragment::isBlockFlow(Node* node) const
+void ReplacementFragment::removeUnrenderedNodes(Node* holder)
 {
-    RefPtr<RenderingInfo> info = m_renderingInfo.get(node);
-    ASSERT(info);
-    if (!info)
-        return false;
-    
-    return info->isBlockFlow();
+    Vector<Node*> unrendered;
+
+    for (Node* node = holder->firstChild(); node; node = node->traverseNextNode(holder))
+        if (!isNodeRendered(node) && !isTableStructureNode(node))
+            unrendered.append(node);
+
+    size_t n = unrendered.size();
+    for (size_t i = 0; i < n; ++i)
+        removeNode(unrendered[i]);
 }
 
-static String &matchNearestBlockquoteColorString()
+void ReplacementFragment::removeInterchangeNodes(Node* startNode)
 {
-    static String matchNearestBlockquoteColorString = "match";
-    return matchNearestBlockquoteColorString;
+    Node* node = startNode;
+    Node* newlineAtStartNode = 0;
+    Node* newlineAtEndNode = 0;
+    while (node) {
+        Node *next = node->traverseNextNode();
+        if (isInterchangeNewlineNode(node)) {
+            if (next || node == startNode) {
+                m_hasInterchangeNewlineAtStart = true;
+                newlineAtStartNode = node;
+            }
+            else {
+                m_hasInterchangeNewlineAtEnd = true;
+                newlineAtEndNode = node;
+            }
+        }
+        else if (isInterchangeConvertedSpaceSpan(node)) {
+            RefPtr<Node> n = 0;
+            while ((n = node->firstChild())) {
+                removeNode(n);
+                insertNodeBefore(n.get(), node);
+            }
+            removeNode(node);
+            if (n)
+                next = n->traverseNextNode();
+        }
+        node = next;
+    }
+
+    if (newlineAtStartNode)
+        removeNode(newlineAtStartNode);
+    if (newlineAtEndNode)
+        removeNode(newlineAtEndNode);
 }
 
-void ReplaceSelectionCommand::fixupNodeStyles(const NodeVector& nodes, const RenderingInfoMap& renderingInfo)
+ReplaceSelectionCommand::ReplaceSelectionCommand(Document* document, PassRefPtr<DocumentFragment> fragment,
+        bool selectReplacement, bool smartReplace, bool matchStyle, bool preventNesting, bool movingParagraph,
+        EditAction editAction) 
+    : CompositeEditCommand(document),
+      m_selectReplacement(selectReplacement), 
+      m_smartReplace(smartReplace),
+      m_matchStyle(matchStyle),
+      m_documentFragment(fragment),
+      m_preventNesting(preventNesting),
+      m_movingParagraph(movingParagraph),
+      m_editAction(editAction)
 {
-    // This function uses the mapped "desired style" to apply the additional style needed, if any,
-    // to make the node have the desired style.
-
-    updateLayout();
-    
-    NodeVector::const_iterator e = nodes.end();
-    for (NodeVector::const_iterator it = nodes.begin(); it != e; ++it) {
-        Node *node = (*it).get();
-        RefPtr<RenderingInfo> info = renderingInfo.get(node);
-        ASSERT(info);
-        if (!info)
-            continue;
-        CSSMutableStyleDeclaration *desiredStyle = info->style();
-        ASSERT(desiredStyle);
+}
 
-        if (!node->inDocument())
-            continue;
+bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart)
+{
+    VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
+    VisiblePosition prev = startOfInsertedContent.previous(true);
+    if (prev.isNull())
+        return false;
+        
+    return !selectionStartWasStartOfParagraph && 
+           !fragmentHasInterchangeNewlineAtStart &&
+           isStartOfParagraph(startOfInsertedContent) && 
+           !startOfInsertedContent.deepEquivalent().node()->hasTagName(brTag) &&
+           shouldMerge(startOfInsertedContent, prev);
+}
 
-        // The desiredStyle declaration tells what style this node wants to be.
-        // Compare that to the style that it is right now in the document.
-        Position pos(node, 0);
-        RefPtr<CSSComputedStyleDeclaration> currentStyle = pos.computedStyle();
+bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
+{
+    VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
+    VisiblePosition next = endOfInsertedContent.next(true);
+    if (next.isNull())
+        return false;
 
-        // Check for the special "match nearest blockquote color" property and resolve to the correct
-        // color if necessary.
-        String matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
-        if (matchColorCheck == matchNearestBlockquoteColorString()) {
-            Node *blockquote = nearestMailBlockquote(node);
-            Position pos(blockquote ? blockquote : node->document()->documentElement(), 0);
-            RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
-            String desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
-            String nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
-            if (desiredColor != nearestColor)
-                desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
-        }
-        desiredStyle->removeProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
+    return !selectionEndWasEndOfParagraph &&
+           isEndOfParagraph(endOfInsertedContent) && 
+           !endOfInsertedContent.deepEquivalent().node()->hasTagName(brTag) &&
+           shouldMerge(endOfInsertedContent, next);
+}
 
-        currentStyle->diff(desiredStyle);
-        
-        // Only add in block properties if the node is at the start of a 
-        // paragraph. This matches AppKit.
-        if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
-            desiredStyle->removeBlockProperties();
-        
-        // If the desiredStyle is non-zero length, that means the current style differs
-        // from the desired by the styles remaining in the desiredStyle declaration.
-        if (desiredStyle->length() > 0)
-            applyStyle(desiredStyle, Position(node, 0), Position(node, maxDeepOffset(node)));
-    }
+static bool isMailPasteAsQuotationNode(const Node* node)
+{
+    return node && node->hasTagName(blockquoteTag) && node->isElementNode() && static_cast<const Element*>(node)->getAttribute(classAttr) == ApplePasteAsQuotation;
 }
 
-static PassRefPtr<CSSMutableStyleDeclaration> styleForNode(Node *node)
+// Wrap CompositeEditCommand::removeNodePreservingChildren() so we can update the nodes we track
+void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
 {
-    if (!node || !node->inDocument())
-        return 0;
-        
-    RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(node, 0).computedStyle();
-    RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
-
-    // In either of the color-matching tests below, set the color to a pseudo-color that will
-    // make the content take on the color of the nearest-enclosing blockquote (if any) after
-    // being pasted in.
-    if (Node *blockquote = nearestMailBlockquote(node)) {
-        RefPtr<CSSComputedStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle();
-        bool match = (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
-        if (match) {
-            style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
-            return style.release();
-        }
-    }
-    Node *documentElement = node->document()->documentElement();
-    RefPtr<CSSComputedStyleDeclaration> documentStyle = Position(documentElement, 0).computedStyle();
-    bool match = (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
-    if (match)
-        style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
-        
-    return style.release();
+    if (m_firstNodeInserted == node)
+        m_firstNodeInserted = node->traverseNextNode();
+    if (m_lastLeafInserted == node)
+        m_lastLeafInserted = node->lastChild() ? node->lastChild() : node->traverseNextSibling();
+    CompositeEditCommand::removeNodePreservingChildren(node);
 }
 
-void ReplacementFragment::saveRenderingInfo(Node *holder)
+// Wrap CompositeEditCommand::removeNodeAndPruneAncestors() so we can update the nodes we track
+void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
 {
-    m_document->updateLayoutIgnorePendingStylesheets();
+    // prepare in case m_firstNodeInserted and/or m_lastLeafInserted get removed
+    // FIXME: shouldn't m_lastLeafInserted be adjusted using traversePreviousNode()?
+    Node* afterFirst = m_firstNodeInserted ? m_firstNodeInserted->traverseNextSibling() : 0;
+    Node* afterLast = m_lastLeafInserted ? m_lastLeafInserted->traverseNextSibling() : 0;
     
-    if (m_matchStyle) {
-        // No style restoration will be done, so we don't need to save styles or keep a node vector.
-        for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
-            m_renderingInfo.add(node, new RenderingInfo(0, node->isBlockFlow()));
-    } else {
-        for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
-            m_renderingInfo.add(node, new RenderingInfo(styleForNode(node), node->isBlockFlow()));
-            m_nodes.append(node);
-        }
-    }
+    CompositeEditCommand::removeNodeAndPruneAncestors(node);
+    
+    // adjust m_firstNodeInserted and m_lastLeafInserted since either or both may have been removed
+    if (m_lastLeafInserted && !m_lastLeafInserted->inDocument())
+        m_lastLeafInserted = afterLast;
+    if (m_firstNodeInserted && !m_firstNodeInserted->inDocument())
+        m_firstNodeInserted = m_lastLeafInserted && m_lastLeafInserted->inDocument() ? afterFirst : 0;
 }
 
-void ReplacementFragment::removeUnrenderedNodes(Node *holder)
+bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& from, const VisiblePosition& to)
 {
-    DeprecatedPtrList<Node> unrendered;
-
-    for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
-        if (!isNodeRendered(node) && !isTableStructureNode(node))
-            unrendered.append(node);
+    if (from.isNull() || to.isNull())
+        return false;
+        
+    Node* fromNode = from.deepEquivalent().node();
+    Node* toNode = to.deepEquivalent().node();
+    Node* fromNodeBlock = enclosingBlock(fromNode);
+    return !enclosingNodeOfType(from.deepEquivalent(), &isMailPasteAsQuotationNode) &&
+           fromNodeBlock && (!fromNodeBlock->hasTagName(blockquoteTag) || isMailBlockquote(fromNodeBlock))  &&
+           enclosingListChild(fromNode) == enclosingListChild(toNode) &&
+           enclosingTableCell(from.deepEquivalent()) == enclosingTableCell(from.deepEquivalent()) &&
+           // Don't merge to or from a position before or after a block because it would
+           // be a no-op and cause infinite recursion.
+           !isBlock(fromNode) && !isBlock(toNode);
+}
+
+// Style rules that match just inserted elements could change their appearance, like
+// a div inserted into a document with div { display:inline; }.
+void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance()
+{
+    for (RefPtr<Node> node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
+        // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance
+        if (isStyleSpan(node.get())) {
+            HTMLElement* e = static_cast<HTMLElement*>(node.get());
+            // There are other styles that style rules can give to style spans,
+            // but these are the two important ones because they'll prevent
+            // inserted content from appearing in the right paragraph.
+            // FIXME: Hyatt is concerned that selectively using display:inline will give inconsistent
+            // results. We already know one issue because td elements ignore their display property
+            // in quirks mode (which Mail.app is always in). We should look for an alternative.
+            if (isBlock(e))
+                e->getInlineStyleDecl()->setProperty(CSSPropertyDisplay, CSSValueInline);
+            if (e->renderer() && e->renderer()->style()->floating() != FNONE)
+                e->getInlineStyleDecl()->setProperty(CSSPropertyFloat, CSSValueNone);
+        }
+        if (node == m_lastLeafInserted)
+            break;
     }
-
-    for (DeprecatedPtrListIterator<Node> it(unrendered); it.current(); ++it)
-        removeNode(it.current());
 }
 
-int ReplacementFragment::renderedBlocks(Node *holder)
+void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds()
 {
-    int count = 0;
-    Node *prev = 0;
-    for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
-        if (node->isBlockFlow()) {
-            if (!prev) {
-                count++;
-                prev = node;
-            }
-        } else {
-            Node *block = node->enclosingBlockFlowElement();
-            if (block != prev) {
-                count++;
-                prev = block;
-            }
+    document()->updateLayoutIgnorePendingStylesheets();
+    if (!m_lastLeafInserted->renderer() && 
+        m_lastLeafInserted->isTextNode() && 
+        !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), selectTag) && 
+        !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), scriptTag)) {
+        if (m_firstNodeInserted == m_lastLeafInserted) {
+            removeNode(m_lastLeafInserted.get());
+            m_lastLeafInserted = 0;
+            m_firstNodeInserted = 0;
+            return;
         }
+        RefPtr<Node> previous = m_lastLeafInserted->traversePreviousNode();
+        removeNode(m_lastLeafInserted.get());
+        m_lastLeafInserted = previous;
     }
     
-    return count;
+    // We don't have to make sure that m_firstNodeInserted isn't inside a select or script element, because
+    // it is a top level node in the fragment and the user can't insert into those elements.
+    if (!m_firstNodeInserted->renderer() && 
+        m_firstNodeInserted->isTextNode()) {
+        if (m_firstNodeInserted == m_lastLeafInserted) {
+            removeNode(m_firstNodeInserted.get());
+            m_firstNodeInserted = 0;
+            m_lastLeafInserted = 0;
+            return;
+        }
+        RefPtr<Node> next = m_firstNodeInserted->traverseNextSibling();
+        removeNode(m_firstNodeInserted.get());
+        m_firstNodeInserted = next;
+    }
 }
 
-void ReplacementFragment::removeStyleNodes()
+void ReplaceSelectionCommand::handlePasteAsQuotationNode()
 {
-    // Since style information has been computed and cached away in
-    // computeStylesUsingTestRendering(), these style nodes can be removed, since
-    // the correct styles will be added back in fixupNodeStyles().
-    Node *node = m_fragment->firstChild();
-    while (node) {
-        Node *next = node->traverseNextNode();
-        // This list of tags change the appearance of content
-        // in ways we can add back on later with CSS, if necessary.
-        //  FIXME: This list is incomplete
-        if (node->hasTagName(bTag) || 
-            node->hasTagName(bigTag) || 
-            node->hasTagName(centerTag) || 
-            node->hasTagName(fontTag) || 
-            node->hasTagName(iTag) || 
-            node->hasTagName(sTag) || 
-            node->hasTagName(smallTag) || 
-            node->hasTagName(strikeTag) || 
-            node->hasTagName(subTag) || 
-            node->hasTagName(supTag) || 
-            node->hasTagName(ttTag) || 
-            node->hasTagName(uTag) || 
-            isStyleSpan(node)) {
-            removeNodePreservingChildren(node);
-        }
-        // need to skip tab span because fixupNodeStyles() is not called
-        // when replace is matching style
-        else if (node->isHTMLElement() && !isTabSpanNode(node)) {
-            HTMLElement *elem = static_cast<HTMLElement *>(node);
-            CSSMutableStyleDeclaration *inlineStyleDecl = elem->inlineStyleDecl();
-            if (inlineStyleDecl) {
-                inlineStyleDecl->removeBlockProperties();
-                inlineStyleDecl->removeInheritableProperties();
-            }
-        }
-        node = next;
-    }
+    Node* node = m_firstNodeInserted.get();
+    if (isMailPasteAsQuotationNode(node))
+        removeNodeAttribute(static_cast<Element*>(node), classAttr);
 }
 
-RenderingInfo::RenderingInfo(PassRefPtr<CSSMutableStyleDeclaration> style, bool isBlockFlow = false)
-    : m_style(style), m_isBlockFlow(isBlockFlow)
+VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent()
 {
+    Node* lastNode = m_lastLeafInserted.get();
+    Node* enclosingSelect = enclosingNodeWithTag(Position(lastNode, 0), selectTag);
+    if (enclosingSelect)
+        lastNode = enclosingSelect;
+    return VisiblePosition(Position(lastNode, maxDeepOffset(lastNode)));
 }
 
-ReplaceSelectionCommand::ReplaceSelectionCommand(Document *document, DocumentFragment *fragment, bool selectReplacement, bool smartReplace, bool matchStyle) 
-    : CompositeEditCommand(document),
-      m_selectReplacement(selectReplacement), 
-      m_smartReplace(smartReplace),
-      m_matchStyle(matchStyle),
-      m_documentFragment(fragment)
+VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent()
 {
+    // Return the inserted content's first VisiblePosition.
+    return VisiblePosition(nextCandidate(positionBeforeNode(m_firstNodeInserted.get())));
 }
 
-ReplaceSelectionCommand::~ReplaceSelectionCommand()
+// Remove style spans before insertion if they are unnecessary.  It's faster because we'll 
+// avoid doing a layout.
+static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
 {
+    Node* topNode = fragment.firstChild();
+    
+    // Handling this case is more complicated (see handleStyleSpans) and doesn't receive the optimization.
+    if (isMailPasteAsQuotationNode(topNode))
+        return false;
+    
+    // Either there are no style spans in the fragment or a WebKit client has added content to the fragment
+    // before inserting it.  Look for and handle style spans after insertion.
+    if (!isStyleSpan(topNode))
+        return false;
+    
+    Node* sourceDocumentStyleSpan = topNode;
+    RefPtr<Node> copiedRangeStyleSpan = sourceDocumentStyleSpan->firstChild();
+    
+    RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = rangeCompliantEquivalent(insertionPos).computedStyle()->copyInheritableProperties();
+    String styleText = styleAtInsertionPos->cssText();
+    
+    if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) {
+        fragment.removeNodePreservingChildren(sourceDocumentStyleSpan);
+        if (!isStyleSpan(copiedRangeStyleSpan.get()))
+            return true;
+    }
+        
+    if (isStyleSpan(copiedRangeStyleSpan.get()) && styleText == static_cast<Element*>(copiedRangeStyleSpan.get())->getAttribute(styleAttr)) {
+        fragment.removeNodePreservingChildren(copiedRangeStyleSpan.get());
+        return true;
+    }
+    
+    return false;
+}
+
+// At copy time, WebKit wraps copied content in a span that contains the source document's 
+// default styles.  If the copied Range inherits any other styles from its ancestors, we put 
+// those styles on a second span.
+// This function removes redundant styles from those spans, and removes the spans if all their 
+// styles are redundant. 
+// We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>.
+// We should remove styles from spans that are overridden by all of their children, either here
+// or at copy time.
+void ReplaceSelectionCommand::handleStyleSpans()
+{
+    Node* sourceDocumentStyleSpan = 0;
+    Node* copiedRangeStyleSpan = 0;
+    // The style span that contains the source document's default style should be at
+    // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
+    // so search for the top level style span instead of assuming it's at the top.
+    for (Node* node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
+        if (isStyleSpan(node)) {
+            sourceDocumentStyleSpan = node;
+            // If the copied Range's common ancestor had user applied inheritable styles
+            // on it, they'll be on a second style span, just below the one that holds the 
+            // document defaults.
+            if (isStyleSpan(node->firstChild()))
+                copiedRangeStyleSpan = node->firstChild();
+            break;
+        }
+    }
+    
+    // There might not be any style spans if we're pasting from another application or if 
+    // we are here because of a document.execCommand("InsertHTML", ...) call.
+    if (!sourceDocumentStyleSpan)
+        return;
+        
+    RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy();
+    Node* context = sourceDocumentStyleSpan->parentNode();
+    
+    // If Mail wraps the fragment with a Paste as Quotation blockquote, styles from that element are
+    // allowed to override those from the source document, see <rdar://problem/4930986>.
+    if (isMailPasteAsQuotationNode(context)) {
+        RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(context)->copyInheritableProperties();
+        RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(context->parentNode())->copyInheritableProperties();
+        parentStyle->diff(blockquoteStyle.get());
+
+        DeprecatedValueListConstIterator<CSSProperty> end;
+        for (DeprecatedValueListConstIterator<CSSProperty> it = blockquoteStyle->valuesIterator(); it != end; ++it) {
+            const CSSProperty& property = *it;
+            sourceDocumentStyle->removeProperty(property.id());
+        }        
+
+        context = context->parentNode();
+    }
+    
+    RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties();
+    contextStyle->diff(sourceDocumentStyle.get());
+    
+    // Remove block properties in the span's style. This prevents properties that probably have no effect 
+    // currently from affecting blocks later if the style is cloned for a new block element during a future 
+    // editing operation.
+    // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked
+    // with block styles by the editing engine used to style them.  WebKit doesn't do this, but others might.
+    sourceDocumentStyle->removeBlockProperties();
+    
+    // The styles on sourceDocumentStyleSpan are all redundant, and there is no copiedRangeStyleSpan
+    // to consider.  We're finished.
+    if (sourceDocumentStyle->length() == 0 && !copiedRangeStyleSpan) {
+        removeNodePreservingChildren(sourceDocumentStyleSpan);
+        return;
+    }
+    
+    // There are non-redundant styles on sourceDocumentStyleSpan, but there is no
+    // copiedRangeStyleSpan.  Clear the redundant styles from sourceDocumentStyleSpan
+    // and return.
+    if (sourceDocumentStyle->length() > 0 && !copiedRangeStyleSpan) {
+        setNodeAttribute(static_cast<Element*>(sourceDocumentStyleSpan), styleAttr, sourceDocumentStyle->cssText());
+        return;
+    }
+    
+    RefPtr<CSSMutableStyleDeclaration> copiedRangeStyle = static_cast<HTMLElement*>(copiedRangeStyleSpan)->getInlineStyleDecl()->copy();
+    
+    // We're going to put sourceDocumentStyleSpan's non-redundant styles onto copiedRangeStyleSpan,
+    // as long as they aren't overridden by ones on copiedRangeStyleSpan.
+    sourceDocumentStyle->merge(copiedRangeStyle.get(), true);
+    copiedRangeStyle = sourceDocumentStyle;
+    
+    removeNodePreservingChildren(sourceDocumentStyleSpan);
+    
+    // Remove redundant styles.
+    context = copiedRangeStyleSpan->parentNode();
+    contextStyle = computedStyle(context)->copyInheritableProperties();
+    contextStyle->diff(copiedRangeStyle.get());
+    
+    // See the comments above about removing block properties.
+    copiedRangeStyle->removeBlockProperties();
+
+    // All the styles on copiedRangeStyleSpan are redundant, remove it.
+    if (copiedRangeStyle->length() == 0) {
+        removeNodePreservingChildren(copiedRangeStyleSpan);
+        return;
+    }
+    
+    // Clear the redundant styles from the span's style attribute.
+    // FIXME: If font-family:-webkit-monospace is non-redundant, then the font-size should stay, even if it
+    // appears redundant.
+    setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->cssText());
 }
 
 void ReplaceSelectionCommand::doApply()
 {
-    // collect information about the current selection, prior to deleting the selection
     Selection selection = endingSelection();
     ASSERT(selection.isCaretOrRange());
+    ASSERT(selection.start().node());
+    if (selection.isNone() || !selection.start().node())
+        return;
     
-    if (!selection.isContentRichlyEditable())
-        m_matchStyle = true;
-    
-    ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection.rootEditableElement());
+    bool selectionIsPlainText = !selection.isContentRichlyEditable();
     
-    if (fragment.type() == EmptyFragment)
-        return;
+    Element* currentRoot = selection.rootEditableElement();
+    ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
     
     if (m_matchStyle)
         m_insertionStyle = styleAtPosition(selection.start());
     
-    VisiblePosition visibleStart(selection.start(), selection.affinity());
-    VisiblePosition visibleEnd(selection.end(), selection.affinity());
-    bool startAtStartOfBlock = isStartOfBlock(visibleStart);
-    bool startAtEndOfBlock = isEndOfBlock(visibleStart);
-    Node *startBlock = selection.start().node()->enclosingBlockFlowElement();
-    Node *endBlock = selection.end().node()->enclosingBlockFlowElement();
-
-    // decide whether to later merge content into the startBlock
-    bool mergeStart = false;
-    if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
-        // empty editable subtree, need to mergeStart so that fragment ends up
-        // merged into the editable subtree rather than adding more levels of block nesting
-        mergeStart = true;
-    } else {
-        // merge if current selection starts inside a paragraph, or there is only one block and no interchange newline to add
-        mergeStart = !fragment.hasInterchangeNewlineAtStart() && 
-            (!isStartOfParagraph(visibleStart) || (!fragment.hasInterchangeNewlineAtEnd() && !fragment.hasMoreThanOneBlock()));
-        
-        // This is a workaround for this bug:
-        // <rdar://problem/4013642> Copied quoted word does not paste as a quote if pasted at the start of a line
-        // We need more powerful logic in this whole mergeStart code for this case to come out right without
-        // breaking other cases.
-        if (isStartOfParagraph(visibleStart) && isMailBlockquote(fragment.firstChild()))
-            mergeStart = false;
-        
-        // prevent first list item from getting merged into target, thereby pulled out of list
-        // NOTE: ideally, we'd check for "first visible position in list" here,
-        // but we cannot.  Fragments do not have any visible positions.  Instead, we
-        // assume that the mergeStartNode() contains the first visible content to paste.
-        // Any better ideas?
-        if (mergeStart) {
-            for (Node *n = fragment.mergeStartNode(); n; n = n->parentNode()) {
-                if (isListElement(n)) {
-                    mergeStart = false;
-                    break;
-                }
-            }
-        }
-    }
+    VisiblePosition visibleStart = selection.visibleStart();
+    VisiblePosition visibleEnd = selection.visibleEnd();
     
-    // decide whether to later append nodes to the end
-    Node *beyondEndNode = 0;
-    if (!isEndOfParagraph(visibleEnd) && !fragment.hasInterchangeNewlineAtEnd() &&
-       (startBlock != endBlock || fragment.hasMoreThanOneBlock()))
-        beyondEndNode = selection.end().downstream().node();
-
-    Position startPos = selection.start();
+    bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
+    bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
+    
+    Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().node());
+    
+    if (selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph ||
+        startBlock == currentRoot ||
+        startBlock && startBlock->renderer() && startBlock->renderer()->isListItem() ||
+        selectionIsPlainText)
+        m_preventNesting = false;
+    
+    Position insertionPos = selection.start();
     
-    // delete the current range selection, or insert paragraph for caret selection, as needed
     if (selection.isRange()) {
-        bool mergeBlocksAfterDelete = !(fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd() || fragment.hasMoreThanOneBlock());
-        deleteSelection(false, mergeBlocksAfterDelete);
-        updateLayout();
-        visibleStart = VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY);
+        // When the end of the selection being pasted into is at the end of a paragraph, and that selection
+        // spans multiple blocks, not merging may leave an empty line.
+        // When the start of the selection being pasted into is at the start of a block, not merging 
+        // will leave hanging block(s).
+        bool mergeBlocksAfterDelete = isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
+        // FIXME: We should only expand to include fully selected special elements if we are copying a 
+        // selection and pasting it on top of itself.
+        deleteSelection(false, mergeBlocksAfterDelete, true, false);
+        visibleStart = endingSelection().visibleStart();
         if (fragment.hasInterchangeNewlineAtStart()) {
             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
                 if (!isEndOfDocument(visibleStart))
                     setEndingSelection(visibleStart.next());
-            } else {
+            } else
                 insertParagraphSeparator();
-                setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
-            }
         }
-        startPos = endingSelection().start();
-    } 
-    else {
+        insertionPos = endingSelection().start();
+    } else {
         ASSERT(selection.isCaret());
         if (fragment.hasInterchangeNewlineAtStart()) {
-            if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
-                if (!isEndOfDocument(visibleStart))
-                    setEndingSelection(visibleStart.next());
-            } else {
+            VisiblePosition next = visibleStart.next(true);
+            if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull())
+                setEndingSelection(next);
+            else 
                 insertParagraphSeparator();
-                setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
-            }
         }
         // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block.
         // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret.  
         // As long as the  div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>, 
         // not <div>xbar<div>bar</div><div>bazx</div></div>
-        // FIXME: If this code is really about preventing block nesting, then the check should be !isEndOfBlock(visibleStart) and we 
-        // should split the block in two, instead of inserting a paragraph separator. In the meantime, it appears that code below 
-        // depends on this split happening when the paste position is not the start or end of a paragraph.
-        if (fragment.hasMoreThanOneBlock() && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
+        if (m_preventNesting && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
             insertParagraphSeparator();
-            setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
+            setEndingSelection(endingSelection().visibleStart().previous());
         }
-        startPos = endingSelection().start();
+        insertionPos = endingSelection().start();
     }
-
-    if (startAtStartOfBlock && startBlock->inDocument())
-        startPos = Position(startBlock, 0);
-
-    // paste into run of tabs splits the tab span
-    startPos = positionOutsideTabSpan(startPos);
     
-    // paste at start or end of link goes outside of link
-    startPos = positionAvoidingSpecialElementBoundary(startPos);
+    // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world.
+    prepareWhitespaceAtPositionForSplit(insertionPos);
+    
+    // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after 
+    // p that maps to the same visible position as p (since in the case where a br is at the end of a block and collapsed 
+    // away, there are positions after the br which map to the same visible position as [br, 0]).  
+    Node* endBR = insertionPos.downstream().node()->hasTagName(brTag) ? insertionPos.downstream().node() : 0;
+    VisiblePosition originalVisPosBeforeEndBR;
+    if (endBR)
+        originalVisPosBeforeEndBR = VisiblePosition(endBR, 0, DOWNSTREAM).previous();
+    
+    startBlock = enclosingBlock(insertionPos.node());
+    
+    // Adjust insertionPos to prevent nesting.
+    if (m_preventNesting && startBlock) {
+        ASSERT(startBlock != currentRoot);
+        VisiblePosition visibleInsertionPos(insertionPos);
+        if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd()))
+            insertionPos = positionAfterNode(startBlock);
+        else if (isStartOfBlock(visibleInsertionPos))
+            insertionPos = positionBeforeNode(startBlock);
+    }
 
-    Frame *frame = document()->frame();
+    // Paste into run of tabs splits the tab span.
+    insertionPos = positionOutsideTabSpan(insertionPos);
+    
+    // Paste at start or end of link goes outside of link.
+    insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
+    
+    // FIXME: Can this wait until after the operation has been performed?  There doesn't seem to be
+    // any work performed after this that queries or uses the typing style.
+    if (Frame* frame = document()->frame())
+        frame->clearTypingStyle();
     
-    // FIXME: Improve typing style.
-    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    frame->clearTypingStyle();
-    setTypingStyle(0);    
+    bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
     
-    // done if there is nothing to add
-    if (!fragment.firstChild())
+    // We're finished if there is nothing to add.
+    if (fragment.isEmpty() || !fragment.firstChild())
         return;
     
-    // check for a line placeholder, and store it away for possible removal later.
-    Node *block = startPos.node()->enclosingBlockFlowElement();
-    Node *linePlaceholder = findBlockPlaceholder(block);
-    if (!linePlaceholder) {
-        Position downstream = startPos.downstream();
-        // NOTE: the check for brTag offset 0 could be false negative after
-        // positionAvoidingSpecialElementBoundary() because "downstream" is
-        // now a "second deepest position"
-        downstream = positionAvoidingSpecialElementBoundary(downstream);
-        if (downstream.node()->hasTagName(brTag) && downstream.offset() == 0 && 
-            fragment.hasInterchangeNewlineAtEnd() &&
-            isStartOfParagraph(VisiblePosition(downstream, VP_DEFAULT_AFFINITY)))
-            linePlaceholder = downstream.node();
-    }
+    // 1) Insert the content.
+    // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>.
+    // 3) Merge the start of the added content with the content before the position being pasted into.
+    // 4) Do one of the following: a) expand the last br if the fragment ends with one and it collapsed,
+    // b) merge the last paragraph of the incoming fragment with the paragraph that contained the 
+    // end of the selection that was pasted into, or c) handle an interchange newline at the end of the 
+    // incoming fragment.
+    // 5) Add spaces for smart replace.
+    // 6) Select the replacement if requested, and match style if requested.
     
-    // check whether to "smart replace" needs to add leading and/or trailing space
-    bool addLeadingSpace = false;
-    bool addTrailingSpace = false;
-    // FIXME: We need the affinity for startPos and endPos, but Position::downstream
-    // and Position::upstream do not give it
-    if (m_smartReplace) {
-        VisiblePosition visiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
-        assert(visiblePos.isNotNull());
-        addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfParagraph(visiblePos);
-        if (addLeadingSpace) {
-            QChar previousChar = visiblePos.previous().character();
-            if (!previousChar.isNull()) {
-                addLeadingSpace = !frame->isCharacterSmartReplaceExempt(previousChar, true);
-            }
-        }
-        addTrailingSpace = startPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfParagraph(visiblePos);
-        if (addTrailingSpace) {
-            QChar thisChar = visiblePos.character();
-            if (!thisChar.isNull()) {
-                addTrailingSpace = !frame->isCharacterSmartReplaceExempt(thisChar, false);
-            }
-        }
+    VisiblePosition startOfInsertedContent, endOfInsertedContent;
+    
+    RefPtr<Node> refNode = fragment.firstChild();
+    RefPtr<Node> node = refNode->nextSibling();
+    
+    fragment.removeNode(refNode);
+    insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos);
+    
+    while (node) {
+        Node* next = node->nextSibling();
+        fragment.removeNode(node);
+        insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
+        refNode = node;
+        node = next;
     }
     
-    // There are five steps to adding the content: merge blocks at start, add remaining blocks,
-    // add "smart replace" space, handle trailing newline, clean up.
-    
-    // initially, we say the insertion point is the start of selection
-    updateLayout();
-    Position insertionPos = startPos;
-
-    // step 1: merge content into the start block
-    if (mergeStart) {
-        RefPtr<Node> refNode = fragment.mergeStartNode();
-        if (refNode) {
-            Node *parent = refNode->parentNode();
-            RefPtr<Node> node = refNode->nextSibling();
-            fragment.removeNode(refNode);
-            insertNodeAtAndUpdateNodesInserted(refNode.get(), startPos.node(), startPos.offset());
-            while (node && !fragment.isBlockFlow(node.get())) {
-                Node *next = node->nextSibling();
-                fragment.removeNode(node);
-                insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
-                refNode = node;
-                node = next;
-            }
+    removeUnrenderedTextNodesAtEnds();
+    
+    negateStyleRulesThatAffectAppearance();
+    
+    if (!handledStyleSpans)
+        handleStyleSpans();
+    
+    // Mutation events (bug 20161) may have already removed the inserted content
+    if (!m_firstNodeInserted || !m_firstNodeInserted->inDocument())
+        return;
+    
+    endOfInsertedContent = positionAtEndOfInsertedContent();
+    startOfInsertedContent = positionAtStartOfInsertedContent();
+    
+    // We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
+    // didn't have a br after it, so the inserted content ended up in the same paragraph.
+    if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.offset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
+        insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
+    
+    Position lastPositionToSelect;
+    
+    bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
 
-            // remove any ancestors we emptied, except the root itself which cannot be removed
-            while (parent && parent->parentNode() && parent->childNodeCount() == 0) {
-                Node *nextParent = parent->parentNode();
-                fragment.removeNode(parent);
-                parent = nextParent;
-            }
+    if (shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR))
+        removeNodeAndPruneAncestors(endBR);
+    
+    if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart())) {
+        // Bail to avoid infinite recursion.
+        if (m_movingParagraph) {
+            // setting display:inline does not work for td elements in quirks mode
+            ASSERT(m_firstNodeInserted->hasTagName(tdTag));
+            return;
         }
+        VisiblePosition destination = startOfInsertedContent.previous();
+        VisiblePosition startOfParagraphToMove = startOfInsertedContent;
         
-        // update insertion point to be at the end of the last block inserted
-        if (m_lastNodeInserted) {
-            updateLayout();
-            insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
-        }
+        // Merging the the first paragraph of inserted content with the content that came
+        // before the selection that was pasted into would also move content after 
+        // the selection that was pasted into if: only one paragraph was being pasted, 
+        // and it was not wrapped in a block, the selection that was pasted into ended 
+        // at the end of a block and the next paragraph didn't start at the start of a block.
+        // Insert a line break just after the inserted content to separate it from what 
+        // comes after and prevent that from happening.
+        VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
+        if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove)
+            insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent.deepEquivalent());
+        
+        // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes.  The nodes are
+        // only ever used to create positions where inserted content starts/ends.
+        moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
+        m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().downstream().node();
+        if (!m_lastLeafInserted->inDocument())
+            m_lastLeafInserted = endingSelection().visibleEnd().deepEquivalent().upstream().node();
     }
+            
+    endOfInsertedContent = positionAtEndOfInsertedContent();
+    startOfInsertedContent = positionAtStartOfInsertedContent();
     
-    // step 2 : merge everything remaining in the fragment
-    if (fragment.firstChild()) {
-        RefPtr<Node> refNode = fragment.firstChild();
-        RefPtr<Node> node = refNode ? refNode->nextSibling() : 0;
-        Node *insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
-        Node* insertionRoot = insertionPos.node()->rootEditableElement();
-        bool insertionBlockIsRoot = insertionBlock == insertionRoot;
-        VisiblePosition visibleInsertionPos(insertionPos, DOWNSTREAM);
-        fragment.removeNode(refNode);
-        if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isStartOfBlock(visibleInsertionPos))
-            insertNodeBeforeAndUpdateNodesInserted(refNode.get(), insertionBlock);
-        else if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isEndOfBlock(visibleInsertionPos)) {
-            insertNodeAfterAndUpdateNodesInserted(refNode.get(), insertionBlock);
-        } else if (m_lastNodeInserted && !fragment.isBlockFlow(refNode.get())) {
-            // A non-null m_lastNodeInserted means we've done merging above.  That means everything in the first paragraph 
-            // of the fragment has been merged with everything up to the start of the paragraph where the paste was performed.  
-            // refNode is the first node in the second paragraph of the fragment to paste.  Since it's inline, we can't 
-            // insert it at insertionPos, because it wouldn't end up in its own paragraph.
-
-            // FIXME: Code above does paragraph splitting and so we are assured that visibleInsertionPos is the end of
-            // a paragraph, but the above splitting should eventually be only about preventing nesting.
-            ASSERT(isEndOfParagraph(visibleInsertionPos));
-            VisiblePosition next = visibleInsertionPos.next();
-            if (next.isNull() || next.deepEquivalent().node()->rootEditableElement() != insertionRoot) {
-                setEndingSelection(visibleInsertionPos);
-                insertParagraphSeparator();
-                next = visibleInsertionPos.next();
+    if (interchangeNewlineAtEnd) {
+        VisiblePosition next = endOfInsertedContent.next(true);
+
+        if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) {
+            if (!isStartOfParagraph(endOfInsertedContent)) {
+                setEndingSelection(endOfInsertedContent);
+                // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph
+                // block's style seems to annoy users.
+                insertParagraphSeparator(true);
+
+                // Select up to the paragraph separator that was added.
+                lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
+                updateNodesInserted(lastPositionToSelect.node());
             }
-            
-            Position pos = next.deepEquivalent().downstream();
-            insertNodeAtAndUpdateNodesInserted(refNode.get(), pos.node(), pos.offset());
         } else {
-            insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos.node(), insertionPos.offset());
+            // Select up to the beginning of the next paragraph.
+            lastPositionToSelect = next.deepEquivalent().downstream();
         }
+            
+    } else if (shouldMergeEnd(selectionEndWasEndOfParagraph)) {
+        // Bail to avoid infinite recursion.
+        if (m_movingParagraph) {
+            ASSERT_NOT_REACHED();
+            return;
+        }
+        // Merging two paragraphs will destroy the moved one's block styles.  Always move forward to preserve
+        // the block style of the paragraph already in the document, unless the paragraph to move would include the
+        // what was the start of the selection that was pasted into.
+        bool mergeForward = !inSameParagraph(startOfInsertedContent, endOfInsertedContent) || isStartOfParagraph(startOfInsertedContent);
         
-        while (node) {
-            Node *next = node->nextSibling();
-            fragment.removeNode(node);
-            insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
-            refNode = node;
-            node = next;
+        VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
+        VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
+
+        moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
+        // Merging forward will remove m_lastLeafInserted from the document.
+        // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes.  The nodes are
+        // only ever used to create positions where inserted content starts/ends.
+        if (mergeForward) {
+            m_lastLeafInserted = destination.previous().deepEquivalent().node();
+            if (!m_firstNodeInserted->inDocument())
+                m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().node();
         }
-        updateLayout();
-        insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
     }
-
-    // step 3 : handle "smart replace" whitespace
-    if (addTrailingSpace && m_lastNodeInserted) {
-        updateLayout();
-        Position pos(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
-        bool needsTrailingSpace = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
-        if (needsTrailingSpace) {
-            if (m_lastNodeInserted->isTextNode()) {
-                Text *text = static_cast<Text *>(m_lastNodeInserted.get());
-                insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
-                insertionPos = Position(text, text->length());
-            }
-            else {
-                RefPtr<Node> node = document()->createEditingTextNode(nonBreakingSpaceString());
-                insertNodeAfterAndUpdateNodesInserted(node.get(), m_lastNodeInserted.get());
-                insertionPos = Position(node.get(), 1);
-            }
-        }
+    
+    handlePasteAsQuotationNode();
+    
+    endOfInsertedContent = positionAtEndOfInsertedContent();
+    startOfInsertedContent = positionAtStartOfInsertedContent();
+    
+    // Add spaces for smart replace.
+    if (m_smartReplace && currentRoot) {
+        // Disable smart replace for password fields.
+        Node* start = currentRoot->shadowAncestorNode();
+        if (start->hasTagName(inputTag) && static_cast<HTMLInputElement*>(start)->inputType() == HTMLInputElement::PASSWORD)
+            m_smartReplace = false;
     }
-
-    if (addLeadingSpace && m_firstNodeInserted) {
-        updateLayout();
-        Position pos(m_firstNodeInserted.get(), 0);
-        bool needsLeadingSpace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
-        if (needsLeadingSpace) {
-            if (m_firstNodeInserted->isTextNode()) {
-                Text *text = static_cast<Text *>(m_firstNodeInserted.get());
-                insertTextIntoNode(text, 0, nonBreakingSpaceString());
+    if (m_smartReplace) {
+        bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
+                                  !isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
+        if (needsTrailingSpace) {
+            RenderObject* renderer = m_lastLeafInserted->renderer();
+            bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
+            Node* endNode = positionAtEndOfInsertedContent().deepEquivalent().upstream().node();
+            if (endNode->isTextNode()) {
+                Text* text = static_cast<Text*>(endNode);
+                insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
             } else {
-                RefPtr<Node> node = document()->createEditingTextNode(nonBreakingSpaceString());
-                insertNodeBeforeAndUpdateNodesInserted(node.get(), m_firstNodeInserted.get());
+                RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
+                insertNodeAfterAndUpdateNodesInserted(node.get(), endNode);
             }
         }
-    }
     
-    Position lastPositionToSelect;
-
-    // step 4 : handle trailing newline
-    if (fragment.hasInterchangeNewlineAtEnd()) {
-        removeLinePlaceholderIfNeeded(linePlaceholder);
-
-        if (!m_lastNodeInserted) {
-            lastPositionToSelect = endingSelection().end().downstream();
-        }
-        else {
-            bool insertParagraph = false;
-            VisiblePosition pos(insertionPos, VP_DEFAULT_AFFINITY);
-            
-            if (startBlock == endBlock && !fragment.isBlockFlow(m_lastTopNodeInserted.get())) {
-                insertParagraph = true;
-            } else {
-                // Handle end-of-document case.
-                updateLayout();
-                if (isEndOfDocument(pos))
-                    insertParagraph = true;
-            }
-            if (insertParagraph) {
-                setEndingSelection(insertionPos, DOWNSTREAM);
-                insertParagraphSeparator();
-                VisiblePosition next = pos.next();
-
-                // Select up to the paragraph separator that was added.
-                lastPositionToSelect = next.deepEquivalent().downstream();
-                updateNodesInserted(lastPositionToSelect.node());
+        bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
+                                 !isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
+        if (needsLeadingSpace) {
+            RenderObject* renderer = m_lastLeafInserted->renderer();
+            bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
+            Node* startNode = positionAtStartOfInsertedContent().deepEquivalent().downstream().node();
+            if (startNode->isTextNode()) {
+                Text* text = static_cast<Text*>(startNode);
+                insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
             } else {
-                // Select up to the preexising paragraph separator.
-                VisiblePosition next = pos.next();
-                lastPositionToSelect = next.deepEquivalent().downstream();
-            }
-        }
-    } else {
-        if (m_lastNodeInserted && m_lastNodeInserted->hasTagName(brTag) && !document()->inStrictMode()) {
-            updateLayout();
-            VisiblePosition pos(Position(m_lastNodeInserted.get(), 1), DOWNSTREAM);
-            if (isEndOfBlock(pos)) {
-                Node *next = m_lastNodeInserted->traverseNextNode();
-                bool hasTrailingBR = next && next->hasTagName(brTag) && m_lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
-                if (!hasTrailingBR) {
-                    // Insert an "extra" BR at the end of the block. 
-                    insertNodeBefore(createBreakElement(document()).get(), m_lastNodeInserted.get());
-                }
+                RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
+                // Don't updateNodesInserted.  Doing so would set m_lastLeafInserted to be the node containing the 
+                // leading space, but m_lastLeafInserted is supposed to mark the end of pasted content.
+                insertNodeBefore(node.get(), startNode);
+                // FIXME: Use positions to track the start/end of inserted content.
+                m_firstNodeInserted = node;
             }
         }
-
-        // FIXME: This is a bad way to move a paragraph.  This could share code with DeleteSelectionCommand::mergeParagraphs().
-        if (beyondEndNode) {
-            updateLayout();
-            RenderingInfoMap renderingInfo;
-            NodeVector nodes;
-            Node* node = beyondEndNode->enclosingInlineElement();
-            Node* refNode = m_lastNodeInserted.get();
-            
-            while (node) {
-                if (node->isBlockFlowOrBlockTable())
-                    break;
-                    
-                Node *next = node->nextSibling();
-                nodes.append(node);
-                renderingInfo.add(node, new RenderingInfo(styleForNode(node)));
-                removeNodeAndPruneAncestors(node);
-                // No need to update inserted node variables.
-                insertNodeAfter(node, refNode);
-                refNode = node;
-                // We want to move the first BR we see, so check for that here.
-                if (node->hasTagName(brTag))
-                    break;
-                node = next;
-            }
-
-            fixupNodeStyles(nodes, renderingInfo);
-        }
     }
     
-    if (!m_matchStyle)
-        fixupNodeStyles(fragment.nodes(), fragment.renderingInfo());
     completeHTMLReplacement(lastPositionToSelect);
-    
-    // step 5 : mop up
-    removeLinePlaceholderIfNeeded(linePlaceholder);
 }
 
-void ReplaceSelectionCommand::removeLinePlaceholderIfNeeded(Node *linePlaceholder)
+bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR)
 {
-    if (!linePlaceholder)
-        return;
+    if (!endBR || !endBR->inDocument())
+        return false;
         
-    updateLayout();
-    if (linePlaceholder->inDocument()) {
-        VisiblePosition placeholderPos(linePlaceholder, linePlaceholder->renderer()->caretMinOffset(), DOWNSTREAM);
-        if (placeholderPos.next().isNull() ||
-            !(isStartOfParagraph(placeholderPos) && isEndOfParagraph(placeholderPos))) {
-            
-            removeNodeAndPruneAncestors(linePlaceholder);
-        }
-    }
+    VisiblePosition visiblePos(Position(endBR, 0));
+    
+    // Don't remove the br if nothing was inserted.
+    if (visiblePos.previous() == originalVisPosBeforeEndBR)
+        return false;
+    
+    // Remove the br if it is collapsed away and so is unnecessary.
+    if (!document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
+        return true;
+        
+    // A br that was originally holding a line open should be displaced by inserted content or turned into a line break.
+    // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
+    return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
 }
 
 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
@@ -862,32 +936,18 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi
     Position start;
     Position end;
 
-    if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
-        // Find the last leaf.
-        Node *lastLeaf = m_lastNodeInserted.get();
-        while (1) {
-            Node *nextChild = lastLeaf->lastChild();
-            if (!nextChild)
-                break;
-            lastLeaf = nextChild;
-        }
-    
-        // Find the first leaf.
-        Node *firstLeaf = m_firstNodeInserted.get();
-        while (1) {
-            Node *nextChild = firstLeaf->firstChild();
-            if (!nextChild)
-                break;
-            firstLeaf = nextChild;
-        }
+    // FIXME: This should never not be the case.
+    if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastLeafInserted && m_lastLeafInserted->inDocument()) {
         
-        // Call updateLayout so caretMinOffset and caretMaxOffset return correct values.
-        updateLayout();
-        start = Position(firstLeaf, firstLeaf->caretMinOffset());
-        end = Position(lastLeaf, lastLeaf->caretMaxOffset());
+        start = positionAtStartOfInsertedContent().deepEquivalent();
+        end = positionAtEndOfInsertedContent().deepEquivalent();
+        
+        // FIXME (11475): Remove this and require that the creator of the fragment to use nbsps.
+        rebalanceWhitespaceAt(start);
+        rebalanceWhitespaceAt(end);
 
         if (m_matchStyle) {
-            assert(m_insertionStyle);
+            ASSERT(m_insertionStyle);
             applyStyle(m_insertionStyle.get(), start, end);
         }    
         
@@ -901,14 +961,12 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi
     if (m_selectReplacement)
         setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
     else
-        setEndingSelection(end, SEL_DEFAULT_AFFINITY);
-    
-    rebalanceWhitespace();
+        setEndingSelection(Selection(end, SEL_DEFAULT_AFFINITY));
 }
 
 EditAction ReplaceSelectionCommand::editingAction() const
 {
-    return EditActionPaste;
+    return m_editAction;
 }
 
 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
@@ -917,9 +975,9 @@ void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insert
     updateNodesInserted(insertChild);
 }
 
-void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, Node *refChild, int offset)
+void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, const Position& p)
 {
-    insertNodeAt(insertChild, refChild, offset);
+    insertNodeAt(insertChild, p);
     updateNodesInserted(insertChild);
 }
 
@@ -934,14 +992,13 @@ void ReplaceSelectionCommand::updateNodesInserted(Node *node)
     if (!node)
         return;
 
-    m_lastTopNodeInserted = node;
     if (!m_firstNodeInserted)
         m_firstNodeInserted = node;
     
-    if (node == m_lastNodeInserted)
+    if (node == m_lastLeafInserted)
         return;
     
-    m_lastNodeInserted = node->lastDescendant();
+    m_lastLeafInserted = node->lastDescendant();
 }
 
 } // namespace WebCore