Reviewed by Darin Adler.
[WebKit-https.git] / WebCore / editing / ReplaceSelectionCommand.cpp
index f5b9f6b..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
@@ -29,7 +29,9 @@
 #include "ApplyStyleCommand.h"
 #include "BeforeTextInsertedEvent.h"
 #include "CSSComputedStyleDeclaration.h"
+#include "CSSProperty.h"
 #include "CSSPropertyNames.h"
+#include "CSSValueKeywords.h"
 #include "Document.h"
 #include "DocumentFragment.h"
 #include "EditingText.h"
@@ -41,6 +43,7 @@
 #include "HTMLInputElement.h"
 #include "HTMLNames.h"
 #include "SelectionController.h"
+#include "SmartReplace.h"
 #include "TextIterator.h"
 #include "htmlediting.h"
 #include "markup.h"
@@ -51,6 +54,40 @@ namespace WebCore {
 using namespace EventNames;
 using namespace HTMLNames;
 
+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);
@@ -86,7 +123,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
     
     Node* shadowAncestorNode = editableRoot->shadowAncestorNode();
     
-    if (!editableRoot->getHTMLEventListener(webkitBeforeTextInsertedEvent) &&
+    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()) &&
@@ -101,9 +138,9 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
     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()) {
         restoreTestRenderingNodesToFragment(holder.get());
@@ -187,14 +224,17 @@ PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* conte
     
     ExceptionCode ec = 0;
 
-    // Copy the whitespace style from the context onto this element.
+    // 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> contextStyle = new CSSComputedStyleDeclaration(static_cast<Element*>(n));
+        RefPtr<CSSComputedStyleDeclaration> conFontStyle = computedStyle(n);
         CSSStyleDeclaration* style = holder->style();
-        style->setProperty(CSS_PROP_WHITE_SPACE, contextStyle->getPropertyValue(CSS_PROP_WHITE_SPACE), false, ec);
+        style->setProperty(CSSPropertyWhiteSpace, conFontStyle->getPropertyValue(CSSPropertyWhiteSpace), false, ec);
+        ASSERT(ec == 0);
+        style->setProperty(CSSPropertyWebkitUserSelect, conFontStyle->getPropertyValue(CSSPropertyWebkitUserSelect), false, ec);
         ASSERT(ec == 0);
     }
     
@@ -273,7 +313,7 @@ void ReplacementFragment::removeInterchangeNodes(Node* startNode)
 }
 
 ReplaceSelectionCommand::ReplaceSelectionCommand(Document* document, PassRefPtr<DocumentFragment> fragment,
-        bool selectReplacement, bool smartReplace, bool matchStyle, bool preventNesting,
+        bool selectReplacement, bool smartReplace, bool matchStyle, bool preventNesting, bool movingParagraph,
         EditAction editAction) 
     : CompositeEditCommand(document),
       m_selectReplacement(selectReplacement), 
@@ -281,6 +321,7 @@ ReplaceSelectionCommand::ReplaceSelectionCommand(Document* document, PassRefPtr<
       m_matchStyle(matchStyle),
       m_documentFragment(fragment),
       m_preventNesting(preventNesting),
+      m_movingParagraph(movingParagraph),
       m_editAction(editAction)
 {
 }
@@ -312,12 +353,12 @@ bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
            shouldMerge(endOfInsertedContent, next);
 }
 
-static bool isMailPasteAsQuotationNode(Node* node)
+static bool isMailPasteAsQuotationNode(const Node* node)
 {
-    return node && node->hasTagName(blockquoteTag) && node->isElementNode() && static_cast<Element*>(node)->getAttribute(classAttr) == ApplePasteAsQuotation;
+    return node && node->hasTagName(blockquoteTag) && node->isElementNode() && static_cast<const Element*>(node)->getAttribute(classAttr) == ApplePasteAsQuotation;
 }
 
-// Virtual method used so that ReplaceSelectionCommand can update the node's it tracks.
+// Wrap CompositeEditCommand::removeNodePreservingChildren() so we can update the nodes we track
 void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
 {
     if (m_firstNodeInserted == node)
@@ -327,6 +368,23 @@ void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
     CompositeEditCommand::removeNodePreservingChildren(node);
 }
 
+// Wrap CompositeEditCommand::removeNodeAndPruneAncestors() so we can update the nodes we track
+void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
+{
+    // 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;
+    
+    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;
+}
+
 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& from, const VisiblePosition& to)
 {
     if (from.isNull() || to.isNull())
@@ -335,84 +393,70 @@ bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& from, const Vis
     Node* fromNode = from.deepEquivalent().node();
     Node* toNode = to.deepEquivalent().node();
     Node* fromNodeBlock = enclosingBlock(fromNode);
-    return !enclosingNodeOfType(fromNode, &isMailPasteAsQuotationNode) &&
+    return !enclosingNodeOfType(from.deepEquivalent(), &isMailPasteAsQuotationNode) &&
            fromNodeBlock && (!fromNodeBlock->hasTagName(blockquoteTag) || isMailBlockquote(fromNodeBlock))  &&
            enclosingListChild(fromNode) == enclosingListChild(toNode) &&
-           enclosingTableCell(fromNode) == enclosingTableCell(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);
 }
 
-void ReplaceSelectionCommand::removeRedundantStyles(Node* mailBlockquoteEnclosingSelectionStart)
+// 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()
 {
-    // There's usually a top level style span that holds the document's default style, push it down.
-    Node* node = m_firstNodeInserted.get();
-    if (isStyleSpan(node) && mailBlockquoteEnclosingSelectionStart) {
-        // Calculate the document default style.
-        RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = Position(mailBlockquoteEnclosingSelectionStart, 0).computedStyle()->copyInheritableProperties();
-        RefPtr<CSSMutableStyleDeclaration> spanStyle = static_cast<HTMLElement*>(node)->inlineStyleDecl();
-        spanStyle->merge(blockquoteStyle.get());  
-    }
-    
-    // Compute and save the non-redundant styles for all HTML elements.
-    // Don't do any mutation here, because that would cause the diffs to trigger layouts.
-    Vector<RefPtr<CSSMutableStyleDeclaration> > styles;
-    Vector<RefPtr<HTMLElement> > elements;
-    for (node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
-        if (node->isHTMLElement() && isStyleSpan(node)) {
-            elements.append(static_cast<HTMLElement*>(node));
-            
-            RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(node->parentNode())->copyInheritableProperties();
-            RefPtr<CSSMutableStyleDeclaration> style = computedStyle(node)->copyInheritableProperties();
-            parentStyle->diff(style.get());
-            
-            styles.append(style.release());
+    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;
     }
-    
-    size_t count = styles.size();
-    for (size_t i = 0; i < count; ++i) {
-        HTMLElement* element = elements[i].get();
-
-        // Handle case where the element was already removed by earlier processing.
-        // It's possible this no longer occurs, but it did happen in an earlier version
-        // that processed elements in a less-determistic order, and I can't prove it
-        // does not occur.
-        if (!element->inDocument())
-            continue;
-
-        // Remove empty style spans.
-        if (isStyleSpan(element) && !element->hasChildNodes()) {
-            if (m_firstNodeInserted == element)
-                m_firstNodeInserted = element->traverseNextSibling();
-            if (m_lastLeafInserted == element)
-                m_lastLeafInserted = element->traverseNextSibling();
-            removeNodeAndPruneAncestors(element);
-            continue;
-        }
+}
 
-        // Remove redundant style tags and style spans.
-        CSSMutableStyleDeclaration* style = styles[i].get();
-        if (style->length() == 0
-                && (isStyleSpan(element)
-                    || element->hasTagName(bTag)
-                    || element->hasTagName(fontTag)
-                    || element->hasTagName(iTag)
-                    || element->hasTagName(uTag))) {
-            removeNodePreservingChildren(element);
-            continue;
+void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds()
+{
+    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;
         }
-
-        // Clear redundant styles from elements.
-        CSSMutableStyleDeclaration* inlineStyleDecl = element->inlineStyleDecl();
-        if (inlineStyleDecl) {
-            CSSComputedStyleDeclaration::removeComputedInheritablePropertiesFrom(inlineStyleDecl);
-            inlineStyleDecl->merge(style, true);
-            setNodeAttribute(element, styleAttr, inlineStyleDecl->cssText());
+        RefPtr<Node> previous = m_lastLeafInserted->traversePreviousNode();
+        removeNode(m_lastLeafInserted.get());
+        m_lastLeafInserted = previous;
+    }
+    
+    // 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;
     }
 }
 
@@ -420,13 +464,13 @@ void ReplaceSelectionCommand::handlePasteAsQuotationNode()
 {
     Node* node = m_firstNodeInserted.get();
     if (isMailPasteAsQuotationNode(node))
-        static_cast<Element*>(node)->setAttribute(classAttr, "");
+        removeNodeAttribute(static_cast<Element*>(node), classAttr);
 }
 
 VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent()
 {
     Node* lastNode = m_lastLeafInserted.get();
-    Node* enclosingSelect = enclosingNodeWithTag(lastNode, selectTag);
+    Node* enclosingSelect = enclosingNodeWithTag(Position(lastNode, 0), selectTag);
     if (enclosingSelect)
         lastNode = enclosingSelect;
     return VisiblePosition(Position(lastNode, maxDeepOffset(lastNode)));
@@ -438,6 +482,146 @@ VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent()
     return VisiblePosition(nextCandidate(positionBeforeNode(m_firstNodeInserted.get())));
 }
 
+// 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()
 {
     Selection selection = endingSelection();
@@ -447,8 +631,6 @@ void ReplaceSelectionCommand::doApply()
         return;
     
     bool selectionIsPlainText = !selection.isContentRichlyEditable();
-    if (selectionIsPlainText)
-        m_matchStyle = true;
     
     Element* currentRoot = selection.rootEditableElement();
     ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
@@ -461,7 +643,6 @@ void ReplaceSelectionCommand::doApply()
     
     bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
     bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
-    Node* mailBlockquoteEnclosingSelectionStart = nearestMailBlockquote(visibleStart.deepEquivalent().node());
     
     Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().node());
     
@@ -481,7 +662,7 @@ void ReplaceSelectionCommand::doApply()
         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);
+        deleteSelection(false, mergeBlocksAfterDelete, true, false);
         visibleStart = endingSelection().visibleStart();
         if (fragment.hasInterchangeNewlineAtStart()) {
             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
@@ -518,6 +699,9 @@ void ReplaceSelectionCommand::doApply()
     // 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());
     
@@ -536,22 +720,13 @@ void ReplaceSelectionCommand::doApply()
     
     // Paste at start or end of link goes outside of link.
     insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
-
-    Frame *frame = document()->frame();
     
-    // FIXME: Improve typing style.
-    // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    frame->clearTypingStyle();
-    setTypingStyle(0);    
+    // 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();
     
-    // Remove the top level style span if its unnecessary before inserting it into the document, its faster.
-    RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = insertionPos.computedStyle()->copyInheritableProperties();
-    if (isStyleSpan(fragment.firstChild())) {
-        Node* styleSpan = fragment.firstChild();
-        String styleText = static_cast<Element*>(styleSpan)->getAttribute(styleAttr);
-        if (styleText == styleAtInsertionPos->cssText())
-            fragment.removeNodePreservingChildren(styleSpan);
-    }
+    bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
     
     // We're finished if there is nothing to add.
     if (fragment.isEmpty() || !fragment.firstChild())
@@ -573,7 +748,7 @@ void ReplaceSelectionCommand::doApply()
     RefPtr<Node> node = refNode->nextSibling();
     
     fragment.removeNode(refNode);
-    insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos.node(), insertionPos.offset());
+    insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos);
     
     while (node) {
         Node* next = node->nextSibling();
@@ -583,7 +758,16 @@ void ReplaceSelectionCommand::doApply()
         node = next;
     }
     
-    removeRedundantStyles(mailBlockquoteEnclosingSelectionStart);
+    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();
@@ -591,25 +775,36 @@ void ReplaceSelectionCommand::doApply()
     // 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().node(), startOfInsertedContent.deepEquivalent().offset());
+        insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
     
     Position lastPositionToSelect;
     
     bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
 
-    if (shouldRemoveEndBR(endBR)) {
-        if (interchangeNewlineAtEnd) {
-            interchangeNewlineAtEnd = false;
-            m_lastLeafInserted = endBR;
-            lastPositionToSelect = VisiblePosition(Position(m_lastLeafInserted.get(), 0)).deepEquivalent();
-        } else
-            removeNodeAndPruneAncestors(endBR);
-    }
-        
+    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;
         
+        // 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);
@@ -627,7 +822,9 @@ void ReplaceSelectionCommand::doApply()
         if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) {
             if (!isStartOfParagraph(endOfInsertedContent)) {
                 setEndingSelection(endOfInsertedContent);
-                insertParagraphSeparator();
+                // 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();
@@ -637,17 +834,13 @@ void ReplaceSelectionCommand::doApply()
             // Select up to the beginning of the next paragraph.
             lastPositionToSelect = next.deepEquivalent().downstream();
         }
-
-    } else if (m_lastLeafInserted->hasTagName(brTag)) {
-        // We want to honor the last incoming line break, so, if it will collapse away because of quirks mode, 
-        // add an extra one.
-        // FIXME: This will expand a br inside a block: <div><br></div>
-        // FIXME: Should we expand all incoming brs that collapse because of quirks mode?
-        if (!document()->inStrictMode() && isEndOfBlock(endOfInsertedContent) && !isStartOfParagraph(endOfInsertedContent))
-            insertNodeBeforeAndUpdateNodesInserted(createBreakElement(document()).get(), m_lastLeafInserted.get());
             
     } 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.
@@ -681,7 +874,7 @@ void ReplaceSelectionCommand::doApply()
     }
     if (m_smartReplace) {
         bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
-                                  !frame->isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
+                                  !isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
         if (needsTrailingSpace) {
             RenderObject* renderer = m_lastLeafInserted->renderer();
             bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
@@ -696,7 +889,7 @@ void ReplaceSelectionCommand::doApply()
         }
     
         bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
-                                 !frame->isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
+                                 !isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
         if (needsLeadingSpace) {
             RenderObject* renderer = m_lastLeafInserted->renderer();
             bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
@@ -718,19 +911,24 @@ void ReplaceSelectionCommand::doApply()
     completeHTMLReplacement(lastPositionToSelect);
 }
 
-bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR)
+bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR)
 {
     if (!endBR || !endBR->inDocument())
         return false;
         
     VisiblePosition visiblePos(Position(endBR, 0));
     
-    return
-        // The br is collapsed away and so is unnecessary.
-        !document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos) ||
-        // 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.
-        isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos) && !m_lastLeafInserted->hasTagName(brTag);
+    // 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)
@@ -749,7 +947,7 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi
         rebalanceWhitespaceAt(end);
 
         if (m_matchStyle) {
-            assert(m_insertionStyle);
+            ASSERT(m_insertionStyle);
             applyStyle(m_insertionStyle.get(), start, end);
         }    
         
@@ -777,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);
 }