LayoutTests:
[WebKit-https.git] / WebCore / editing / ReplaceSelectionCommand.cpp
1 /*
2  * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "ReplaceSelectionCommand.h"
28
29 #include "ApplyStyleCommand.h"
30 #include "BeforeTextInsertedEvent.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSPropertyNames.h"
33 #include "Document.h"
34 #include "DocumentFragment.h"
35 #include "EditingText.h"
36 #include "Element.h"
37 #include "Frame.h"
38 #include "HTMLElement.h"
39 #include "HTMLInterchange.h"
40 #include "HTMLNames.h"
41 #include "SelectionController.h"
42 #include "TextIterator.h"
43 #include "htmlediting.h"
44 #include "markup.h"
45 #include "visible_units.h"
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
52     : m_document(document),
53       m_fragment(fragment),
54       m_matchStyle(matchStyle), 
55       m_hasInterchangeNewlineAtStart(false), 
56       m_hasInterchangeNewlineAtEnd(false), 
57       m_hasMoreThanOneBlock(false)
58 {
59     if (!m_document)
60         return;
61     if (!m_fragment)
62         return;
63     Node* firstChild = m_fragment->firstChild();
64     if (!firstChild)
65         return;
66     
67     Element* editableRoot = selection.rootEditableElement();
68     ASSERT(editableRoot);
69     if (!editableRoot)
70         return;
71
72     Node* styleNode = selection.base().node();
73     RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
74     
75     RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange();
76     String text = plainText(range.get());
77     // Give the root a chance to change the text.
78     RefPtr<BeforeTextInsertedEvent> evt = new BeforeTextInsertedEvent(text);
79     ExceptionCode ec = 0;
80     editableRoot->dispatchEvent(evt, ec, true);
81     ASSERT(ec == 0);
82     if (text != evt->text() || !editableRoot->isContentRichlyEditable()) {
83         restoreTestRenderingNodesToFragment(holder.get());
84         removeNode(holder);
85
86         m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
87         firstChild = m_fragment->firstChild();
88         if (!firstChild)
89             return;
90         holder = insertFragmentForTestRendering(styleNode);
91     }
92
93     Node *node = firstChild;
94     Node *newlineAtStartNode = 0;
95     Node *newlineAtEndNode = 0;
96     while (node) {
97         Node *next = node->traverseNextNode();
98         if (isInterchangeNewlineNode(node)) {
99             if (next || node == firstChild) {
100                 m_hasInterchangeNewlineAtStart = true;
101                 newlineAtStartNode = node;
102             }
103             else {
104                 m_hasInterchangeNewlineAtEnd = true;
105                 newlineAtEndNode = node;
106             }
107         }
108         else if (isInterchangeConvertedSpaceSpan(node)) {
109             RefPtr<Node> n = 0;
110             while ((n = node->firstChild())) {
111                 removeNode(n);
112                 insertNodeBefore(n.get(), node);
113             }
114             removeNode(node);
115             if (n)
116                 next = n->traverseNextNode();
117         }
118         node = next;
119     }
120
121     if (newlineAtStartNode)
122         removeNode(newlineAtStartNode);
123     if (newlineAtEndNode)
124         removeNode(newlineAtEndNode);
125     
126     saveRenderingInfo(holder.get());
127     removeUnrenderedNodes(holder.get());
128     m_hasMoreThanOneBlock = renderedBlocks(holder.get()) > 1;
129     restoreTestRenderingNodesToFragment(holder.get());
130     removeNode(holder);
131     removeStyleNodes();
132 }
133
134 bool ReplacementFragment::isEmpty() const
135 {
136     return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
137 }
138
139 Node *ReplacementFragment::firstChild() const 
140
141     return m_fragment->firstChild(); 
142 }
143
144 Node *ReplacementFragment::lastChild() const 
145
146     return m_fragment->lastChild(); 
147 }
148
149 static bool isMailPasteAsQuotationNode(const Node *node)
150 {
151     return node && static_cast<const Element *>(node)->getAttribute("class") == ApplePasteAsQuotation;
152 }
153
154 Node *ReplacementFragment::mergeStartNode() const
155 {
156     Node *node = m_fragment->firstChild();
157     while (node && isBlockFlow(node) && !isMailPasteAsQuotationNode(node))
158         node = node->traverseNextNode();
159     return node;
160 }
161
162 bool ReplacementFragment::isInterchangeNewlineNode(const Node *node)
163 {
164     static String interchangeNewlineClassString(AppleInterchangeNewline);
165     return node && node->hasTagName(brTag) && 
166            static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
167 }
168
169 bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const Node *node)
170 {
171     static String convertedSpaceSpanClassString(AppleConvertedSpace);
172     return node->isHTMLElement() && 
173            static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
174 }
175
176 Node *ReplacementFragment::enclosingBlock(Node *node) const
177 {
178     while (node && !isBlockFlow(node))
179         node = node->parentNode();    
180     return node ? node : m_fragment.get();
181 }
182
183 void ReplacementFragment::removeNodePreservingChildren(Node *node)
184 {
185     if (!node)
186         return;
187
188     while (RefPtr<Node> n = node->firstChild()) {
189         removeNode(n);
190         insertNodeBefore(n.get(), node);
191     }
192     removeNode(node);
193 }
194
195 void ReplacementFragment::removeNode(PassRefPtr<Node> node)
196 {
197     if (!node)
198         return;
199     
200     Node *parent = node->parentNode();
201     if (!parent)
202         return;
203     
204     ExceptionCode ec = 0;
205     parent->removeChild(node.get(), ec);
206     ASSERT(ec == 0);
207 }
208
209 void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
210 {
211     if (!node || !refNode)
212         return;
213         
214     Node *parent = refNode->parentNode();
215     if (!parent)
216         return;
217         
218     ExceptionCode ec = 0;
219     parent->insertBefore(node, refNode, ec);
220     ASSERT(ec == 0);
221 }
222
223 PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context)
224 {
225     Node* body = m_document->body();
226     if (!body)
227         return 0;
228
229     RefPtr<StyledElement> holder = static_pointer_cast<StyledElement>(createDefaultParagraphElement(m_document.get()));
230     
231     ExceptionCode ec = 0;
232
233     // Copy the whitespace style from the context onto this element.
234     Node* n = context;
235     while (n && !n->isElementNode())
236         n = n->parentNode();
237     if (n) {
238         RefPtr<CSSComputedStyleDeclaration> contextStyle = new CSSComputedStyleDeclaration(static_cast<Element*>(n));
239         CSSStyleDeclaration* style = holder->style();
240         style->setProperty(CSS_PROP_WHITE_SPACE, contextStyle->getPropertyValue(CSS_PROP_WHITE_SPACE), false, ec);
241         ASSERT(ec == 0);
242     }
243     
244     holder->appendChild(m_fragment, ec);
245     ASSERT(ec == 0);
246     
247     body->appendChild(holder.get(), ec);
248     ASSERT(ec == 0);
249     
250     m_document->updateLayoutIgnorePendingStylesheets();
251     
252     return holder.release();
253 }
254
255 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder)
256 {
257     if (!holder)
258         return;
259     
260     ExceptionCode ec = 0;
261     while (RefPtr<Node> node = holder->firstChild()) {
262         holder->removeChild(node.get(), ec);
263         ASSERT(ec == 0);
264         m_fragment->appendChild(node.get(), ec);
265         ASSERT(ec == 0);
266     }
267 }
268
269 bool ReplacementFragment::isBlockFlow(Node* node) const
270 {
271     RefPtr<RenderingInfo> info = m_renderingInfo.get(node);
272     ASSERT(info);
273     if (!info)
274         return false;
275     
276     return info->isBlockFlow();
277 }
278
279 static String &matchNearestBlockquoteColorString()
280 {
281     static String matchNearestBlockquoteColorString = "match";
282     return matchNearestBlockquoteColorString;
283 }
284
285 void ReplaceSelectionCommand::fixupNodeStyles(const NodeVector& nodes, const RenderingInfoMap& renderingInfo)
286 {
287     // This function uses the mapped "desired style" to apply the additional style needed, if any,
288     // to make the node have the desired style.
289
290     updateLayout();
291     
292     NodeVector::const_iterator e = nodes.end();
293     for (NodeVector::const_iterator it = nodes.begin(); it != e; ++it) {
294         Node *node = (*it).get();
295         RefPtr<RenderingInfo> info = renderingInfo.get(node);
296         ASSERT(info);
297         if (!info)
298             continue;
299         CSSMutableStyleDeclaration *desiredStyle = info->style();
300         ASSERT(desiredStyle);
301
302         if (!node->inDocument())
303             continue;
304
305         // The desiredStyle declaration tells what style this node wants to be.
306         // Compare that to the style that it is right now in the document.
307         Position pos(node, 0);
308         RefPtr<CSSComputedStyleDeclaration> currentStyle = pos.computedStyle();
309
310         // Check for the special "match nearest blockquote color" property and resolve to the correct
311         // color if necessary.
312         String matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
313         if (matchColorCheck == matchNearestBlockquoteColorString()) {
314             Node *blockquote = nearestMailBlockquote(node);
315             Position pos(blockquote ? blockquote : node->document()->documentElement(), 0);
316             RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
317             String desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
318             String nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
319             if (desiredColor != nearestColor)
320                 desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
321         }
322         desiredStyle->removeProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
323
324         currentStyle->diff(desiredStyle);
325         
326         // Only add in block properties if the node is at the start of a 
327         // paragraph. This matches AppKit.
328         if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
329             desiredStyle->removeBlockProperties();
330         
331         // If the desiredStyle is non-zero length, that means the current style differs
332         // from the desired by the styles remaining in the desiredStyle declaration.
333         if (desiredStyle->length() > 0)
334             applyStyle(desiredStyle, Position(node, 0), Position(node, maxDeepOffset(node)));
335     }
336 }
337
338 static PassRefPtr<CSSMutableStyleDeclaration> styleForNode(Node *node)
339 {
340     if (!node || !node->inDocument())
341         return 0;
342         
343     RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(node, 0).computedStyle();
344     RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
345
346     // In either of the color-matching tests below, set the color to a pseudo-color that will
347     // make the content take on the color of the nearest-enclosing blockquote (if any) after
348     // being pasted in.
349     if (Node *blockquote = nearestMailBlockquote(node)) {
350         RefPtr<CSSComputedStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle();
351         bool match = (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
352         if (match) {
353             style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
354             return style.release();
355         }
356     }
357     Node *documentElement = node->document()->documentElement();
358     RefPtr<CSSComputedStyleDeclaration> documentStyle = Position(documentElement, 0).computedStyle();
359     bool match = (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
360     if (match)
361         style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
362         
363     return style.release();
364 }
365
366 void ReplacementFragment::saveRenderingInfo(Node *holder)
367 {
368     m_document->updateLayoutIgnorePendingStylesheets();
369     
370     if (m_matchStyle) {
371         // No style restoration will be done, so we don't need to save styles or keep a node vector.
372         for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
373             m_renderingInfo.add(node, new RenderingInfo(0, node->isBlockFlow()));
374     } else {
375         for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
376             m_renderingInfo.add(node, new RenderingInfo(styleForNode(node), node->isBlockFlow()));
377             m_nodes.append(node);
378         }
379     }
380 }
381
382 void ReplacementFragment::removeUnrenderedNodes(Node *holder)
383 {
384     DeprecatedPtrList<Node> unrendered;
385
386     for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
387         if (!isNodeRendered(node) && !isTableStructureNode(node))
388             unrendered.append(node);
389     }
390
391     for (DeprecatedPtrListIterator<Node> it(unrendered); it.current(); ++it)
392         removeNode(it.current());
393 }
394
395 // FIXME: This counts two blocks for <span><div>foo</div></span>.  Get rid of uses of hasMoreThanOneBlock so that we can get rid of this function.
396 int ReplacementFragment::renderedBlocks(Node *holder)
397 {
398     int count = 0;
399     Node *prev = 0;
400     for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
401         if (node->isBlockFlow()) {
402             if (!prev) {
403                 count++;
404                 prev = node;
405             }
406         } else {
407             Node *block = node->enclosingBlockFlowElement();
408             if (block != prev) {
409                 count++;
410                 prev = block;
411             }
412         }
413     }
414     
415     return count;
416 }
417
418 void ReplacementFragment::removeStyleNodes()
419 {
420     // Since style information has been computed and cached away in
421     // computeStylesUsingTestRendering(), these style nodes can be removed, since
422     // the correct styles will be added back in fixupNodeStyles().
423     Node *node = m_fragment->firstChild();
424     while (node) {
425         Node *next = node->traverseNextNode();
426         // This list of tags change the appearance of content
427         // in ways we can add back on later with CSS, if necessary.
428         //  FIXME: This list is incomplete
429         if (node->hasTagName(bTag) || 
430             node->hasTagName(bigTag) || 
431             node->hasTagName(centerTag) || 
432             node->hasTagName(fontTag) || 
433             node->hasTagName(iTag) || 
434             node->hasTagName(sTag) || 
435             node->hasTagName(smallTag) || 
436             node->hasTagName(strikeTag) || 
437             node->hasTagName(subTag) || 
438             node->hasTagName(supTag) || 
439             node->hasTagName(ttTag) || 
440             node->hasTagName(uTag) || 
441             isStyleSpan(node)) {
442             removeNodePreservingChildren(node);
443         }
444         // need to skip tab span because fixupNodeStyles() is not called
445         // when replace is matching style
446         else if (node->isHTMLElement() && !isTabSpanNode(node)) {
447             HTMLElement *elem = static_cast<HTMLElement *>(node);
448             CSSMutableStyleDeclaration *inlineStyleDecl = elem->inlineStyleDecl();
449             if (inlineStyleDecl) {
450                 inlineStyleDecl->removeBlockProperties();
451                 inlineStyleDecl->removeInheritableProperties();
452             }
453         }
454         node = next;
455     }
456 }
457
458 RenderingInfo::RenderingInfo(PassRefPtr<CSSMutableStyleDeclaration> style, bool isBlockFlow = false)
459     : m_style(style), m_isBlockFlow(isBlockFlow)
460 {
461 }
462
463 ReplaceSelectionCommand::ReplaceSelectionCommand(Document *document, DocumentFragment *fragment, bool selectReplacement, bool smartReplace, bool matchStyle, bool forceMergeStart, EditAction editAction) 
464     : CompositeEditCommand(document),
465       m_selectReplacement(selectReplacement), 
466       m_smartReplace(smartReplace),
467       m_matchStyle(matchStyle),
468       m_documentFragment(fragment),
469       m_forceMergeStart(forceMergeStart),
470       m_editAction(editAction)
471 {
472 }
473
474 ReplaceSelectionCommand::~ReplaceSelectionCommand()
475 {
476 }
477
478 // FIXME: This will soon operate on the fragment after it's been inserted so that it can check renderers and create visible positions.
479 bool ReplaceSelectionCommand::shouldMergeStart(const ReplacementFragment& incomingFragment, const Selection& destinationSelection)
480 {
481     if (m_forceMergeStart)
482         return true;
483         
484     VisiblePosition visibleStart = destinationSelection.visibleStart();
485     Node* startBlock = destinationSelection.start().node()->enclosingBlockFlowElement();
486
487     // <rdar://problem/4013642> Copied quoted word does not paste as a quote if pasted at the start of a line    
488     if (isStartOfParagraph(visibleStart) && isMailBlockquote(incomingFragment.firstChild()))
489         return false;
490     
491     // Don't pull content out of a list item.
492     // FIXMEs: Use an enclosing element getter that uses the render tree once this function is called after the fragment
493     // that's been placed into the document.
494     if (enclosingList(incomingFragment.mergeStartNode()) || enclosingNodeWithTag(incomingFragment.mergeStartNode(), tdTag))
495         return false;
496     
497     // Merge if this is an empty editable subtree, to prevent an extra level of block nesting.
498     if (startBlock == startBlock->rootEditableElement() && isStartOfBlock(visibleStart) && isEndOfBlock(visibleStart))
499         return true;
500     
501     if (!incomingFragment.hasInterchangeNewlineAtStart() && 
502         (!isStartOfParagraph(visibleStart) || !incomingFragment.hasInterchangeNewlineAtEnd() && !incomingFragment.hasMoreThanOneBlock()))
503        return true;
504     
505     return false;
506 }
507
508 bool ReplaceSelectionCommand::shouldMergeEnd(const VisiblePosition& endOfInsertedContent, bool selectionEndWasEndOfParagraph)
509 {
510     Node* endNode = endOfInsertedContent.deepEquivalent().node();
511     Node* nextNode = endOfInsertedContent.next().deepEquivalent().node();
512     // FIXME: Unify the naming scheme for these enclosing element getters.
513     return !selectionEndWasEndOfParagraph &&
514            isEndOfParagraph(endOfInsertedContent) && 
515            nearestMailBlockquote(endNode) == nearestMailBlockquote(nextNode) &&
516            enclosingListChild(endNode) == enclosingListChild(nextNode) &&
517            enclosingTableCell(endNode) == enclosingTableCell(nextNode) &&
518            !endNode->hasTagName(hrTag);
519 }
520
521 void ReplaceSelectionCommand::doApply()
522 {
523     // collect information about the current selection, prior to deleting the selection
524     Selection selection = endingSelection();
525     ASSERT(selection.isCaretOrRange());
526     ASSERT(selection.start().node());
527     if (selection.isNone() || !selection.start().node())
528         return;
529     
530     if (!selection.isContentRichlyEditable())
531         m_matchStyle = true;
532     
533     Element* currentRoot = selection.rootEditableElement();
534     ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
535     
536     if (fragment.isEmpty())
537         return;
538     
539     if (m_matchStyle)
540         m_insertionStyle = styleAtPosition(selection.start());
541     
542     VisiblePosition visibleStart(selection.start(), selection.affinity());
543     VisiblePosition visibleEnd(selection.end(), selection.affinity());
544     bool startAtStartOfBlock = isStartOfBlock(visibleStart);
545     Node* startBlock = selection.start().node()->enclosingBlockFlowElement();
546
547     // Whether the first paragraph of the incoming fragment should be merged with content from visibleStart to startOfParagraph(visibleStart).
548     bool mergeStart = shouldMergeStart(fragment, selection);
549     
550     bool endWasEndOfParagraph = isEndOfParagraph(visibleEnd);
551
552     Position startPos = selection.start();
553     
554     // delete the current range selection, or insert paragraph for caret selection, as needed
555     if (selection.isRange()) {
556         // When the end of the selection being pasted into is at the end of a paragraph, and that selection
557         // spans multiple blocks, not merging may leave an empty line.
558         // When the start of the selection being pasted into is at the start of a block, not merging 
559         // will leave hanging block(s).
560         bool mergeBlocksAfterDelete = isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
561         deleteSelection(false, mergeBlocksAfterDelete, true);
562         updateLayout();
563         visibleStart = endingSelection().visibleStart();
564         if (fragment.hasInterchangeNewlineAtStart()) {
565             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
566                 if (!isEndOfDocument(visibleStart))
567                     setEndingSelection(visibleStart.next());
568             } else
569                 insertParagraphSeparator();
570         }
571         startPos = endingSelection().start();
572     } 
573     else {
574         ASSERT(selection.isCaret());
575         if (fragment.hasInterchangeNewlineAtStart()) {
576             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
577                 if (!isEndOfDocument(visibleStart))
578                     setEndingSelection(visibleStart.next());
579             } else 
580                 insertParagraphSeparator();
581         }
582         // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block.
583         // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret.  
584         // As long as the  div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>, 
585         // not <div>xbar<div>bar</div><div>bazx</div></div>
586         // FIXME: If this code is really about preventing block nesting, then the check should be !isEndOfBlock(visibleStart) and we 
587         // should split the block in two, instead of inserting a paragraph separator. In the meantime, it appears that code below 
588         // depends on this split happening when the paste position is not the start or end of a paragraph.
589         if (!isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
590             insertParagraphSeparator();
591             setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
592         }
593         startPos = endingSelection().start();
594     }
595     
596     // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after 
597     // 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 
598     // away, there are positions after the br which map to the same visible position as [br, 0]).  
599     Node* endBR = startPos.downstream().node()->hasTagName(brTag) ? startPos.downstream().node() : 0;
600     
601     if (startAtStartOfBlock && startBlock->inDocument())
602         startPos = Position(startBlock, 0);
603
604     // paste into run of tabs splits the tab span
605     startPos = positionOutsideTabSpan(startPos);
606     
607     // paste at start or end of link goes outside of link
608     startPos = positionAvoidingSpecialElementBoundary(startPos);
609
610     Frame *frame = document()->frame();
611     
612     // FIXME: Improve typing style.
613     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
614     frame->clearTypingStyle();
615     setTypingStyle(0);    
616     
617     // done if there is nothing to add
618     if (!fragment.firstChild())
619         return;
620     
621     // 1) Add the first paragraph of the incoming fragment, merging it with the paragraph that contained the 
622     // start of the selection that was pasted into.
623     // 2) Add everything else.
624     // 3) Restore the styles of inserted nodes (since styles were removed during the test insertion).
625     // 4) Do one of the following: a) if there was a br at the end of the fragment, expand it if it collapsed 
626     // because of quirks mode, b) merge the last paragraph of the incoming fragment with the paragraph that contained the 
627     // end of the selection that was pasted into, or c) handle an interchange newline at the end of the 
628     // incoming fragment.
629     // 5) Add spaces for smart replace.
630     // 6) Select the replacement if requested, and match style if requested.
631         
632     // initially, we say the insertion point is the start of selection
633     updateLayout();
634     Position insertionPos = startPos;
635
636     // Add the first paragraph.
637     if (mergeStart) {
638         RefPtr<Node> refNode = fragment.mergeStartNode();
639         if (refNode) {
640             Node *parent = refNode->parentNode();
641             RefPtr<Node> node = refNode->nextSibling();
642             fragment.removeNode(refNode);
643             insertNodeAtAndUpdateNodesInserted(refNode.get(), startPos.node(), startPos.offset());
644             while (node && !fragment.isBlockFlow(node.get())) {
645                 Node *next = node->nextSibling();
646                 fragment.removeNode(node);
647                 insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
648                 refNode = node;
649                 node = next;
650             }
651
652             // remove any ancestors we emptied, except the root itself which cannot be removed
653             while (parent && parent->parentNode() && parent->childNodeCount() == 0) {
654                 Node *nextParent = parent->parentNode();
655                 fragment.removeNode(parent);
656                 parent = nextParent;
657             }
658         }
659         
660         // update insertion point to be at the end of the last block inserted
661         if (m_lastNodeInserted) {
662             updateLayout();
663             insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
664         }
665     }
666     
667     // Add everything else.
668     if (fragment.firstChild()) {
669         RefPtr<Node> refNode = fragment.firstChild();
670         RefPtr<Node> node = refNode ? refNode->nextSibling() : 0;
671         Node* insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
672         Node* insertionRoot = insertionPos.node()->rootEditableElement();
673         bool insertionBlockIsRoot = insertionBlock == insertionRoot;
674         VisiblePosition visibleInsertionPos(insertionPos);
675         fragment.removeNode(refNode);
676         // FIXME: The first two cases need to be rethought.  They're about preventing the nesting of 
677         // incoming blocks in the block where the paste is being performed.  But, avoiding nesting doesn't 
678         // always produce the desired visual result, and the decisions are based on isBlockFlow, which 
679         // we're getting rid of.
680         if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isStartOfBlock(visibleInsertionPos) && !m_lastNodeInserted)
681             insertNodeBeforeAndUpdateNodesInserted(refNode.get(), insertionBlock);
682         else if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isEndOfBlock(visibleInsertionPos))
683             insertNodeAfterAndUpdateNodesInserted(refNode.get(), insertionBlock);
684         else if (m_lastNodeInserted && !fragment.isBlockFlow(refNode.get())) {
685             // A non-null m_lastNodeInserted means we've done merging above.  That means everything in the first paragraph 
686             // of the fragment has been merged with everything up to the start of the paragraph where the paste was performed.  
687             // refNode is the first node in the second paragraph of the fragment to paste.  Since it's inline, we can't 
688             // insert it at insertionPos, because it wouldn't end up in its own paragraph.
689
690             // FIXME: Code above does paragraph splitting and so we are assured that visibleInsertionPos is the end of
691             // a paragraph, but the above splitting should eventually be only about preventing nesting.
692             ASSERT(isEndOfParagraph(visibleInsertionPos));
693             VisiblePosition next = visibleInsertionPos.next();
694             if (next.isNull() || next.rootEditableElement() != insertionRoot) {
695                 setEndingSelection(visibleInsertionPos);
696                 insertParagraphSeparator();
697                 next = visibleInsertionPos.next();
698             }
699             
700             Position pos = next.deepEquivalent().downstream();
701             insertNodeAtAndUpdateNodesInserted(refNode.get(), pos.node(), pos.offset());
702         } else {
703             insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos.node(), insertionPos.offset());
704         }
705         
706         while (node) {
707             Node* next = node->nextSibling();
708             fragment.removeNode(node);
709             insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
710             refNode = node;
711             node = next;
712         }
713         updateLayout();
714         insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
715     }
716
717     Position lastPositionToSelect;
718     
719     bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
720     bool lastNodeInsertedWasBR = m_lastNodeInserted->hasTagName(brTag);
721
722     if (shouldRemoveEndBR(endBR)) {
723         if (interchangeNewlineAtEnd || lastNodeInsertedWasBR) {
724             interchangeNewlineAtEnd = false;
725             lastNodeInsertedWasBR = false;
726             m_lastNodeInserted = endBR;
727             lastPositionToSelect = VisiblePosition(Position(m_lastNodeInserted.get(), 0)).deepEquivalent();
728         } else
729             removeNodeAndPruneAncestors(endBR);
730     }
731     
732     // Styles were removed during the test insertion.  Restore them.
733     if (!m_matchStyle)
734         fixupNodeStyles(fragment.nodes(), fragment.renderingInfo());
735         
736     VisiblePosition endOfInsertedContent(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
737     VisiblePosition startOfInsertedContent(Position(m_firstNodeInserted.get(), 0));    
738     
739     if (interchangeNewlineAtEnd) {
740         VisiblePosition pos(insertionPos);
741         VisiblePosition next = pos.next();
742
743         if (endWasEndOfParagraph || !isEndOfParagraph(pos) || next.rootEditableElement() != currentRoot) {
744             if (!isStartOfParagraph(pos)) {
745                 setEndingSelection(pos.deepEquivalent().downstream(), DOWNSTREAM);
746                 insertParagraphSeparator();
747
748                 // Select up to the paragraph separator that was added.
749                 lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
750                 updateNodesInserted(lastPositionToSelect.node());
751             }
752         } else {
753             // Select up to the beginning of the next paragraph.
754             lastPositionToSelect = next.deepEquivalent().downstream();
755         }
756
757     } else if (lastNodeInsertedWasBR) {
758         // We want to honor the last incoming line break, so, if it will collapse away because of quirks mode, 
759         // add an extra one.
760         // FIXME: This will expand a br inside a block: <div><br></div>
761         // FIXME: Should we expand all incoming brs that collapse because of quirks mode?
762         if (!document()->inStrictMode() && isEndOfBlock(VisiblePosition(Position(m_lastNodeInserted.get(), 0))))
763             insertNodeBeforeAndUpdateNodesInserted(createBreakElement(document()).get(), m_lastNodeInserted.get());
764             
765     } else if (shouldMergeEnd(endOfInsertedContent, endWasEndOfParagraph)) {
766         // Make sure that content after the end of the selection being pasted into is in the same paragraph as the 
767         // last bit of content that was inserted.
768         
769         Position downstreamEnd = endOfInsertedContent.deepEquivalent().downstream();
770         Position upstreamOnePastEnd = endOfInsertedContent.next().deepEquivalent().upstream();
771         Node* node = downstreamEnd.node();
772         int offset = downstreamEnd.offset();
773         if (node && node == upstreamOnePastEnd.node() && offset + 1 == upstreamOnePastEnd.offset() && node->isTextNode()) {
774             // Special case for removing a newline character.
775             Text* textNode = static_cast<Text*>(node);
776             if (textNode->length() == 1)
777                 removeNodeAndPruneAncestors(textNode);
778             else
779                 deleteTextFromNode(textNode, offset, 1);
780         } else {
781             // Merging two paragraphs will destroy the moved one's block styles.  Always move forward to preserve
782             // the block style of the paragraph already in the document, unless the paragraph to move would include the
783             // what was the start of the selection that was pasted into.
784             bool mergeForward = !inSameParagraph(startOfInsertedContent, endOfInsertedContent);
785             
786             VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
787             VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
788
789             moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
790             // Merging forward will remove m_lastNodeInserted from the document.
791             // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes.  The nodes are
792             // only ever used to create positions where inserted content starts/ends.
793             if (mergeForward)
794                 m_lastNodeInserted = destination.previous().deepEquivalent().node();
795         }
796     }
797     
798     endOfInsertedContent = VisiblePosition(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
799     startOfInsertedContent = VisiblePosition(Position(m_firstNodeInserted.get(), 0));    
800     
801     // Add spaces for smart replace.
802     if (m_smartReplace) {
803         bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
804                                   !frame->isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
805         if (needsTrailingSpace) {
806             RenderObject* renderer = m_lastNodeInserted->renderer();
807             bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
808             if (m_lastNodeInserted->isTextNode()) {
809                 Text* text = static_cast<Text*>(m_lastNodeInserted.get());
810                 insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
811             } else {
812                 RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
813                 insertNodeAfterAndUpdateNodesInserted(node.get(), m_lastNodeInserted.get());
814             }
815         }
816     
817         bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
818                                  !frame->isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
819         if (needsLeadingSpace) {
820             RenderObject* renderer = m_lastNodeInserted->renderer();
821             bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
822             if (m_firstNodeInserted->isTextNode()) {
823                 Text* text = static_cast<Text*>(m_firstNodeInserted.get());
824                 insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
825             } else {
826                 RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
827                 // Don't updateNodesInserted.  Doing so would set m_lastNodeInserted to be the node containing the 
828                 // leading space, but m_lastNodeInserted is supposed to mark the end of pasted content.
829                 insertNodeBefore(node.get(), m_firstNodeInserted.get());
830                 // FIXME: Use positions to track the start/end of inserted content.
831                 m_firstNodeInserted = node;
832             }
833         }
834     }
835     
836     completeHTMLReplacement(lastPositionToSelect);
837 }
838
839 bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR)
840 {
841     if (!endBR || !endBR->inDocument())
842         return false;
843         
844     VisiblePosition visiblePos(Position(endBR, 0));
845     
846     return
847         // The br is collapsed away and so is unnecessary.
848         !document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos) ||
849         // A br that was originally holding a line open should be displaced by inserted content.
850         // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
851         isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
852 }
853
854 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
855 {
856     Position start;
857     Position end;
858
859     if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
860         
861         Node* lastLeaf = m_lastNodeInserted->lastDescendant();
862         Node* firstLeaf = m_firstNodeInserted->firstDescendant();
863         
864         start = Position(firstLeaf, 0);
865         end = Position(lastLeaf, maxDeepOffset(lastLeaf));
866         
867         // FIXME: Should we treat all spaces in incoming content as having been rendered?
868         rebalanceWhitespaceAt(start);
869         rebalanceWhitespaceAt(end);
870
871         if (m_matchStyle) {
872             assert(m_insertionStyle);
873             applyStyle(m_insertionStyle.get(), start, end);
874         }    
875         
876         if (lastPositionToSelect.isNotNull())
877             end = lastPositionToSelect;
878     } else if (lastPositionToSelect.isNotNull())
879         start = end = lastPositionToSelect;
880     else
881         return;
882     
883     if (m_selectReplacement)
884         setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
885     else
886         setEndingSelection(end, SEL_DEFAULT_AFFINITY);
887 }
888
889 EditAction ReplaceSelectionCommand::editingAction() const
890 {
891     return m_editAction;
892 }
893
894 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
895 {
896     insertNodeAfter(insertChild, refChild);
897     updateNodesInserted(insertChild);
898 }
899
900 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, Node *refChild, int offset)
901 {
902     insertNodeAt(insertChild, refChild, offset);
903     updateNodesInserted(insertChild);
904 }
905
906 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(Node *insertChild, Node *refChild)
907 {
908     insertNodeBefore(insertChild, refChild);
909     updateNodesInserted(insertChild);
910 }
911
912 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
913 {
914     if (!node)
915         return;
916
917     m_lastTopNodeInserted = node;
918     if (!m_firstNodeInserted)
919         m_firstNodeInserted = node;
920     
921     if (node == m_lastNodeInserted)
922         return;
923     
924     m_lastNodeInserted = node->lastDescendant();
925 }
926
927 } // namespace WebCore