afd25fc82932a78e03f62bd30049cbb4008c02f3
[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 "DocumentFragment.h"
32 #include "Document.h"
33 #include "EditingText.h"
34 #include "Frame.h"
35 #include "HTMLElement.h"
36 #include "VisiblePosition.h"
37 #include "CSSComputedStyleDeclaration.h"
38 #include "css_valueimpl.h"
39 #include "CSSPropertyNames.h"
40 #include "dom2_eventsimpl.h"
41 #include "Range.h"
42 #include "Position.h"
43 #include "HTMLInterchange.h"
44 #include "htmlediting.h"
45 #include "HTMLNames.h"
46 #include "markup.h"
47 #include "RenderObject.h"
48 #include "SelectionController.h"
49 #include "visible_units.h"
50 #include "TextIterator.h"
51 #include <kxmlcore/Assertions.h>
52
53 namespace WebCore {
54
55 using namespace HTMLNames;
56
57 ReplacementFragment::ReplacementFragment(Document *document, DocumentFragment *fragment, bool matchStyle)
58     : m_document(document),
59       m_fragment(fragment),
60       m_matchStyle(matchStyle), 
61       m_hasInterchangeNewlineAtStart(false), 
62       m_hasInterchangeNewlineAtEnd(false), 
63       m_hasMoreThanOneBlock(false)
64 {
65     if (!m_document)
66         return;
67
68     if (!fragment) {
69         m_type = EmptyFragment;
70         return;
71     }
72     
73     Node *firstChild = fragment->firstChild();
74     Node *lastChild = fragment->lastChild();
75
76     if (!firstChild) {
77         m_type = EmptyFragment;
78         return;
79     }
80
81     if (firstChild == lastChild && firstChild->isTextNode()) {
82         m_type = SingleTextNodeFragment;
83         return;
84     }
85     
86     m_type = TreeFragment;
87
88     Node *node = fragment->firstChild();
89     Node *newlineAtStartNode = 0;
90     Node *newlineAtEndNode = 0;
91     while (node) {
92         Node *next = node->traverseNextNode();
93         if (isInterchangeNewlineNode(node)) {
94             if (next || node == fragment->firstChild()) {
95                 m_hasInterchangeNewlineAtStart = true;
96                 newlineAtStartNode = node;
97             }
98             else {
99                 m_hasInterchangeNewlineAtEnd = true;
100                 newlineAtEndNode = node;
101             }
102         }
103         else if (isInterchangeConvertedSpaceSpan(node)) {
104             RefPtr<Node> n = 0;
105             while ((n = node->firstChild())) {
106                 removeNode(n);
107                 insertNodeBefore(n.get(), node);
108             }
109             removeNode(node);
110             if (n)
111                 next = n->traverseNextNode();
112         }
113         node = next;
114     }
115
116     if (newlineAtStartNode)
117         removeNode(newlineAtStartNode);
118     if (newlineAtEndNode)
119         removeNode(newlineAtEndNode);
120         
121     RefPtr<Node> holder = insertFragmentForTestRendering();
122     
123     Element* editableRoot = document->frame() ? document->frame()->selection().rootEditableElement() : 0;
124     ASSERT(editableRoot);
125     if (!editableRoot)
126         return;
127
128     RefPtr<Range> range = new Range(holder->document());
129     ExceptionCode ec = 0;
130     range->selectNodeContents(holder.get(), ec);
131     ASSERT(ec == 0);
132     String text = plainText(range.get());
133     String newText = text.copy();
134     // Give the root a chance to change the text.
135     RefPtr<Event> evt = new BeforeTextInsertedEvent(newText);
136     editableRoot->dispatchEvent(evt, ec, true);
137     if (text != newText || !editableRoot->isContentRichlyEditable()) {
138         removeNode(holder);
139         m_fragment = createFragmentFromText(document, newText.deprecatedString());
140         holder = insertFragmentForTestRendering();
141      }
142     
143     if (!editableRoot->isContentRichlyEditable())
144         m_matchStyle = true;
145     
146     saveRenderingInfo(holder.get());
147     removeUnrenderedNodes(holder.get());
148     m_hasMoreThanOneBlock = renderedBlocks(holder.get()) > 1;
149     restoreTestRenderingNodesToFragment(holder.get());
150     removeNode(holder);
151     removeStyleNodes();
152 }
153
154 ReplacementFragment::~ReplacementFragment()
155 {
156 }
157
158 Node *ReplacementFragment::firstChild() const 
159
160     return m_fragment->firstChild(); 
161 }
162
163 Node *ReplacementFragment::lastChild() const 
164
165     return m_fragment->lastChild(); 
166 }
167
168 static bool isMailPasteAsQuotationNode(const Node *node)
169 {
170     return node && static_cast<const Element *>(node)->getAttribute("class") == ApplePasteAsQuotation;
171 }
172
173 Node *ReplacementFragment::mergeStartNode() const
174 {
175     Node *node = m_fragment->firstChild();
176     while (node && isBlockFlow(node) && !isMailPasteAsQuotationNode(node))
177         node = node->traverseNextNode();
178     return node;
179 }
180
181 bool ReplacementFragment::isInterchangeNewlineNode(const Node *node)
182 {
183     static String interchangeNewlineClassString(AppleInterchangeNewline);
184     return node && node->hasTagName(brTag) && 
185            static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
186 }
187
188 bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const Node *node)
189 {
190     static String convertedSpaceSpanClassString(AppleConvertedSpace);
191     return node->isHTMLElement() && 
192            static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
193 }
194
195 Node *ReplacementFragment::enclosingBlock(Node *node) const
196 {
197     while (node && !isBlockFlow(node))
198         node = node->parentNode();    
199     return node ? node : m_fragment.get();
200 }
201
202 void ReplacementFragment::removeNodePreservingChildren(Node *node)
203 {
204     if (!node)
205         return;
206
207     while (RefPtr<Node> n = node->firstChild()) {
208         removeNode(n);
209         insertNodeBefore(n.get(), node);
210     }
211     removeNode(node);
212 }
213
214 void ReplacementFragment::removeNode(PassRefPtr<Node> node)
215 {
216     if (!node)
217         return;
218     
219     Node *parent = node->parentNode();
220     if (!parent)
221         return;
222     
223     ExceptionCode ec = 0;
224     parent->removeChild(node.get(), ec);
225     ASSERT(ec == 0);
226 }
227
228 void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
229 {
230     if (!node || !refNode)
231         return;
232         
233     Node *parent = refNode->parentNode();
234     if (!parent)
235         return;
236         
237     ExceptionCode ec = 0;
238     parent->insertBefore(node, refNode, ec);
239     ASSERT(ec == 0);
240 }
241
242 PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering()
243 {
244     Node *body = m_document->body();
245     if (!body)
246         return 0;
247
248     RefPtr<Node> holder = createDefaultParagraphElement(m_document.get());
249     
250     ExceptionCode ec = 0;
251     holder->appendChild(m_fragment, ec);
252     ASSERT(ec == 0);
253     
254     body->appendChild(holder.get(), ec);
255     ASSERT(ec == 0);
256     
257     m_document->updateLayoutIgnorePendingStylesheets();
258     
259     return holder.release();
260 }
261
262 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder)
263 {
264     if (!holder)
265         return;
266     
267     ExceptionCode ec = 0;
268     while (RefPtr<Node> node = holder->firstChild()) {
269         holder->removeChild(node.get(), ec);
270         ASSERT(ec == 0);
271         m_fragment->appendChild(node.get(), ec);
272         ASSERT(ec == 0);
273     }
274 }
275
276 bool ReplacementFragment::isBlockFlow(Node* node) const
277 {
278     RefPtr<RenderingInfo> info = m_renderingInfo.get(node);
279     ASSERT(info);
280     if (!info)
281         return false;
282     
283     return info->isBlockFlow();
284 }
285
286 static String &matchNearestBlockquoteColorString()
287 {
288     static String matchNearestBlockquoteColorString = "match";
289     return matchNearestBlockquoteColorString;
290 }
291
292 // FIXME: Move this somewhere so that the other editing operations can use it to clean up after themselves.
293 void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
294 {
295     Node* parent = node->parentNode();
296     removeNode(node);
297     while (parent) {
298         Node* nextParent = parent->parentNode();
299         // If you change this rule you may have to add an updateLayout() here.
300         if (parent->renderer() && parent->renderer()->firstChild())
301             return;
302         removeNode(parent);
303         parent = nextParent;
304     }
305 }
306
307 void ReplaceSelectionCommand::fixupNodeStyles(const NodeVector& nodes, const RenderingInfoMap& renderingInfo)
308 {
309     // This function uses the mapped "desired style" to apply the additional style needed, if any,
310     // to make the node have the desired style.
311
312     updateLayout();
313     
314     NodeVector::const_iterator e = nodes.end();
315     for (NodeVector::const_iterator it = nodes.begin(); it != e; ++it) {
316         Node *node = (*it).get();
317         RefPtr<RenderingInfo> info = renderingInfo.get(node);
318         ASSERT(info);
319         if (!info)
320             continue;
321         CSSMutableStyleDeclaration *desiredStyle = info->style();
322         ASSERT(desiredStyle);
323
324         if (!node->inDocument())
325             continue;
326
327         // The desiredStyle declaration tells what style this node wants to be.
328         // Compare that to the style that it is right now in the document.
329         Position pos(node, 0);
330         RefPtr<CSSComputedStyleDeclaration> currentStyle = pos.computedStyle();
331
332         // Check for the special "match nearest blockquote color" property and resolve to the correct
333         // color if necessary.
334         String matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
335         if (matchColorCheck == matchNearestBlockquoteColorString()) {
336             Node *blockquote = nearestMailBlockquote(node);
337             Position pos(blockquote ? blockquote : node->document()->documentElement(), 0);
338             RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
339             String desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
340             String nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
341             if (desiredColor != nearestColor)
342                 desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
343         }
344         desiredStyle->removeProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
345
346         currentStyle->diff(desiredStyle);
347         
348         // Only add in block properties if the node is at the start of a 
349         // paragraph. This matches AppKit.
350         if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
351             desiredStyle->removeBlockProperties();
352         
353         // If the desiredStyle is non-zero length, that means the current style differs
354         // from the desired by the styles remaining in the desiredStyle declaration.
355         if (desiredStyle->length() > 0)
356             applyStyle(desiredStyle, Position(node, 0), Position(node, maxDeepOffset(node)));
357     }
358 }
359
360 static PassRefPtr<CSSMutableStyleDeclaration> styleForNode(Node *node)
361 {
362     if (!node || !node->inDocument())
363         return 0;
364         
365     RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(node, 0).computedStyle();
366     RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
367
368     // In either of the color-matching tests below, set the color to a pseudo-color that will
369     // make the content take on the color of the nearest-enclosing blockquote (if any) after
370     // being pasted in.
371     if (Node *blockquote = nearestMailBlockquote(node)) {
372         RefPtr<CSSComputedStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle();
373         bool match = (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
374         if (match) {
375             style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
376             return style.release();
377         }
378     }
379     Node *documentElement = node->document()->documentElement();
380     RefPtr<CSSComputedStyleDeclaration> documentStyle = Position(documentElement, 0).computedStyle();
381     bool match = (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
382     if (match)
383         style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
384         
385     return style.release();
386 }
387
388 void ReplacementFragment::saveRenderingInfo(Node *holder)
389 {
390     m_document->updateLayoutIgnorePendingStylesheets();
391     
392     if (m_matchStyle) {
393         // No style restoration will be done, so we don't need to save styles or keep a node vector.
394         for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
395             m_renderingInfo.add(node, new RenderingInfo(0, node->isBlockFlow()));
396     } else {
397         for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
398             m_renderingInfo.add(node, new RenderingInfo(styleForNode(node), node->isBlockFlow()));
399             m_nodes.append(node);
400         }
401     }
402 }
403
404 void ReplacementFragment::removeUnrenderedNodes(Node *holder)
405 {
406     DeprecatedPtrList<Node> unrendered;
407
408     for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
409         if (!isNodeRendered(node) && !isTableStructureNode(node))
410             unrendered.append(node);
411     }
412
413     for (DeprecatedPtrListIterator<Node> it(unrendered); it.current(); ++it)
414         removeNode(it.current());
415 }
416
417 int ReplacementFragment::renderedBlocks(Node *holder)
418 {
419     int count = 0;
420     Node *prev = 0;
421     for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
422         if (node->isBlockFlow()) {
423             if (!prev) {
424                 count++;
425                 prev = node;
426             }
427         } else {
428             Node *block = node->enclosingBlockFlowElement();
429             if (block != prev) {
430                 count++;
431                 prev = block;
432             }
433         }
434     }
435     
436     return count;
437 }
438
439 void ReplacementFragment::removeStyleNodes()
440 {
441     // Since style information has been computed and cached away in
442     // computeStylesUsingTestRendering(), these style nodes can be removed, since
443     // the correct styles will be added back in fixupNodeStyles().
444     Node *node = m_fragment->firstChild();
445     while (node) {
446         Node *next = node->traverseNextNode();
447         // This list of tags change the appearance of content
448         // in ways we can add back on later with CSS, if necessary.
449         //  FIXME: This list is incomplete
450         if (node->hasTagName(bTag) || 
451             node->hasTagName(bigTag) || 
452             node->hasTagName(centerTag) || 
453             node->hasTagName(fontTag) || 
454             node->hasTagName(iTag) || 
455             node->hasTagName(sTag) || 
456             node->hasTagName(smallTag) || 
457             node->hasTagName(strikeTag) || 
458             node->hasTagName(subTag) || 
459             node->hasTagName(supTag) || 
460             node->hasTagName(ttTag) || 
461             node->hasTagName(uTag) || 
462             isStyleSpan(node)) {
463             removeNodePreservingChildren(node);
464         }
465         // need to skip tab span because fixupNodeStyles() is not called
466         // when replace is matching style
467         else if (node->isHTMLElement() && !isTabSpanNode(node)) {
468             HTMLElement *elem = static_cast<HTMLElement *>(node);
469             CSSMutableStyleDeclaration *inlineStyleDecl = elem->inlineStyleDecl();
470             if (inlineStyleDecl) {
471                 inlineStyleDecl->removeBlockProperties();
472                 inlineStyleDecl->removeInheritableProperties();
473             }
474         }
475         node = next;
476     }
477 }
478
479 RenderingInfo::RenderingInfo(PassRefPtr<CSSMutableStyleDeclaration> style, bool isBlockFlow = false)
480     : m_style(style), m_isBlockFlow(isBlockFlow)
481 {
482 }
483
484 ReplaceSelectionCommand::ReplaceSelectionCommand(Document *document, DocumentFragment *fragment, bool selectReplacement, bool smartReplace, bool matchStyle) 
485     : CompositeEditCommand(document), 
486       m_fragment(document, fragment, matchStyle),
487       m_selectReplacement(selectReplacement), 
488       m_smartReplace(smartReplace),
489       m_matchStyle(matchStyle)
490 {
491 }
492
493 ReplaceSelectionCommand::~ReplaceSelectionCommand()
494 {
495 }
496
497 void ReplaceSelectionCommand::doApply()
498 {
499     // collect information about the current selection, prior to deleting the selection
500     Selection selection = endingSelection();
501     ASSERT(selection.isCaretOrRange());
502     
503     if (!selection.isContentRichlyEditable())
504         m_matchStyle = true;
505     
506     if (m_matchStyle)
507         m_insertionStyle = styleAtPosition(selection.start());
508     
509     VisiblePosition visibleStart(selection.start(), selection.affinity());
510     VisiblePosition visibleEnd(selection.end(), selection.affinity());
511     bool startAtStartOfBlock = isStartOfBlock(visibleStart);
512     bool startAtEndOfBlock = isEndOfBlock(visibleStart);
513     bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
514     Node *startBlock = selection.start().node()->enclosingBlockFlowElement();
515     Node *endBlock = selection.end().node()->enclosingBlockFlowElement();
516
517     // decide whether to later merge content into the startBlock
518     bool mergeStart = false;
519     if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
520         // empty editable subtree, need to mergeStart so that fragment ends up
521         // inside the editable subtree rather than just before it
522         // FIXME: Reconcile comment versus mergeStart = false
523         mergeStart = false;
524     } else {
525         // merge if current selection starts inside a paragraph, or there is only one block and no interchange newline to add
526         mergeStart = !m_fragment.hasInterchangeNewlineAtStart() && 
527             (!isStartOfParagraph(visibleStart) || (!m_fragment.hasInterchangeNewlineAtEnd() && !m_fragment.hasMoreThanOneBlock()));
528         
529         // This is a workaround for this bug:
530         // <rdar://problem/4013642> Copied quoted word does not paste as a quote if pasted at the start of a line
531         // We need more powerful logic in this whole mergeStart code for this case to come out right without
532         // breaking other cases.
533         if (isStartOfParagraph(visibleStart) && isMailBlockquote(m_fragment.firstChild()))
534             mergeStart = false;
535         
536         // prevent first list item from getting merged into target, thereby pulled out of list
537         // NOTE: ideally, we'd check for "first visible position in list" here,
538         // but we cannot.  Fragments do not have any visible positions.  Instead, we
539         // assume that the mergeStartNode() contains the first visible content to paste.
540         // Any better ideas?
541         if (mergeStart) {
542             for (Node *n = m_fragment.mergeStartNode(); n; n = n->parentNode()) {
543                 if (isListElement(n)) {
544                     mergeStart = false;
545                     break;
546                 }
547             }
548         }
549     }
550     
551     // decide whether to later append nodes to the end
552     Node *beyondEndNode = 0;
553     if (!isEndOfParagraph(visibleEnd) && !m_fragment.hasInterchangeNewlineAtEnd() &&
554        (startBlock != endBlock || m_fragment.hasMoreThanOneBlock()))
555         beyondEndNode = selection.end().downstream().node();
556
557     Position startPos = selection.start();
558     
559     // delete the current range selection, or insert paragraph for caret selection, as needed
560     if (selection.isRange()) {
561         bool mergeBlocksAfterDelete = !(m_fragment.hasInterchangeNewlineAtStart() || m_fragment.hasInterchangeNewlineAtEnd() || m_fragment.hasMoreThanOneBlock());
562         deleteSelection(false, mergeBlocksAfterDelete);
563         updateLayout();
564         visibleStart = VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY);
565         if (m_fragment.hasInterchangeNewlineAtStart()) {
566             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
567                 if (!isEndOfDocument(visibleStart))
568                     setEndingSelection(visibleStart.next());
569             } else {
570                 insertParagraphSeparator();
571                 setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
572             }
573         }
574         startPos = endingSelection().start();
575     } 
576     else {
577         ASSERT(selection.isCaret());
578         if (m_fragment.hasInterchangeNewlineAtStart()) {
579             if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
580                 if (!isEndOfDocument(visibleStart))
581                     setEndingSelection(visibleStart.next());
582             } else {
583                 insertParagraphSeparator();
584                 setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
585             }
586         }
587         if (!m_fragment.hasInterchangeNewlineAtEnd() && m_fragment.hasMoreThanOneBlock() && 
588             !startAtBlockBoundary && !isEndOfParagraph(visibleEnd)) {
589             // The start and the end need to wind up in separate blocks.
590             // Insert a paragraph separator to make that happen.
591             insertParagraphSeparator();
592             setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
593         }
594         startPos = endingSelection().start();
595     }
596
597     if (startAtStartOfBlock && startBlock->inDocument())
598         startPos = Position(startBlock, 0);
599
600     // paste into run of tabs splits the tab span
601     startPos = positionOutsideTabSpan(startPos);
602     
603     // paste at start or end of link goes outside of link
604     startPos = positionAvoidingSpecialElementBoundary(startPos);
605
606     Frame *frame = document()->frame();
607     
608     // FIXME: Improve typing style.
609     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
610     frame->clearTypingStyle();
611     setTypingStyle(0);    
612     
613     // done if there is nothing to add
614     if (!m_fragment.firstChild())
615         return;
616     
617     // check for a line placeholder, and store it away for possible removal later.
618     Node *block = startPos.node()->enclosingBlockFlowElement();
619     Node *linePlaceholder = findBlockPlaceholder(block);
620     if (!linePlaceholder) {
621         Position downstream = startPos.downstream();
622         // NOTE: the check for brTag offset 0 could be false negative after
623         // positionAvoidingSpecialElementBoundary() because "downstream" is
624         // now a "second deepest position"
625         downstream = positionAvoidingSpecialElementBoundary(downstream);
626         if (downstream.node()->hasTagName(brTag) && downstream.offset() == 0 && 
627             m_fragment.hasInterchangeNewlineAtEnd() &&
628             isStartOfLine(VisiblePosition(downstream, VP_DEFAULT_AFFINITY)))
629             linePlaceholder = downstream.node();
630     }
631     
632     // check whether to "smart replace" needs to add leading and/or trailing space
633     bool addLeadingSpace = false;
634     bool addTrailingSpace = false;
635     // FIXME: We need the affinity for startPos and endPos, but Position::downstream
636     // and Position::upstream do not give it
637     if (m_smartReplace) {
638         VisiblePosition visiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
639         assert(visiblePos.isNotNull());
640         addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfLine(visiblePos);
641         if (addLeadingSpace) {
642             QChar previousChar = visiblePos.previous().character();
643             if (!previousChar.isNull()) {
644                 addLeadingSpace = !frame->isCharacterSmartReplaceExempt(previousChar, true);
645             }
646         }
647         addTrailingSpace = startPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfLine(visiblePos);
648         if (addTrailingSpace) {
649             QChar thisChar = visiblePos.character();
650             if (!thisChar.isNull()) {
651                 addTrailingSpace = !frame->isCharacterSmartReplaceExempt(thisChar, false);
652             }
653         }
654     }
655     
656     // There are five steps to adding the content: merge blocks at start, add remaining blocks,
657     // add "smart replace" space, handle trailing newline, clean up.
658     
659     // initially, we say the insertion point is the start of selection
660     updateLayout();
661     Position insertionPos = startPos;
662
663     // step 1: merge content into the start block
664     if (mergeStart) {
665         Node *refNode = m_fragment.mergeStartNode();
666         if (refNode) {
667             Node *parent = refNode->parentNode();
668             Node *node = refNode->nextSibling();
669             insertNodeAtAndUpdateNodesInserted(refNode, startPos.node(), startPos.offset());
670             while (node && !m_fragment.isBlockFlow(node)) {
671                 Node *next = node->nextSibling();
672                 insertNodeAfterAndUpdateNodesInserted(node, refNode);
673                 refNode = node;
674                 node = next;
675             }
676
677             // remove any ancestors we emptied, except the root itself which cannot be removed
678             while (parent && parent->parentNode() && parent->childNodeCount() == 0) {
679                 Node *nextParent = parent->parentNode();
680                 removeNode(parent);
681                 parent = nextParent;
682             }
683         }
684         
685         // update insertion point to be at the end of the last block inserted
686         if (m_lastNodeInserted) {
687             updateLayout();
688             insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
689         }
690     }
691     
692     // step 2 : merge everything remaining in the fragment
693     if (m_fragment.firstChild()) {
694         Node *refNode = m_fragment.firstChild();
695         Node *node = refNode ? refNode->nextSibling() : 0;
696         Node *insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
697         bool insertionBlockIsRoot = insertionBlock == insertionBlock->rootEditableElement();
698         VisiblePosition visiblePos(insertionPos, DOWNSTREAM);
699         if (!insertionBlockIsRoot && m_fragment.isBlockFlow(refNode) && isStartOfBlock(visiblePos))
700             insertNodeBeforeAndUpdateNodesInserted(refNode, insertionBlock);
701         else if (!insertionBlockIsRoot && m_fragment.isBlockFlow(refNode) && isEndOfBlock(visiblePos)) {
702             insertNodeAfterAndUpdateNodesInserted(refNode, insertionBlock);
703         } else if (m_lastNodeInserted && !m_fragment.isBlockFlow(refNode)) {
704             Position pos = visiblePos.next().deepEquivalent().downstream();
705             insertNodeAtAndUpdateNodesInserted(refNode, pos.node(), pos.offset());
706         } else {
707             insertNodeAtAndUpdateNodesInserted(refNode, insertionPos.node(), insertionPos.offset());
708         }
709         
710         while (node) {
711             Node *next = node->nextSibling();
712             insertNodeAfterAndUpdateNodesInserted(node, refNode);
713             refNode = node;
714             node = next;
715         }
716         updateLayout();
717         insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
718     }
719
720     // step 3 : handle "smart replace" whitespace
721     if (addTrailingSpace && m_lastNodeInserted) {
722         updateLayout();
723         Position pos(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
724         bool needsTrailingSpace = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
725         if (needsTrailingSpace) {
726             if (m_lastNodeInserted->isTextNode()) {
727                 Text *text = static_cast<Text *>(m_lastNodeInserted.get());
728                 insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
729                 insertionPos = Position(text, text->length());
730             }
731             else {
732                 RefPtr<Node> node = document()->createEditingTextNode(nonBreakingSpaceString());
733                 insertNodeAfterAndUpdateNodesInserted(node.get(), m_lastNodeInserted.get());
734                 insertionPos = Position(node.get(), 1);
735             }
736         }
737     }
738
739     if (addLeadingSpace && m_firstNodeInserted) {
740         updateLayout();
741         Position pos(m_firstNodeInserted.get(), 0);
742         bool needsLeadingSpace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
743         if (needsLeadingSpace) {
744             if (m_firstNodeInserted->isTextNode()) {
745                 Text *text = static_cast<Text *>(m_firstNodeInserted.get());
746                 insertTextIntoNode(text, 0, nonBreakingSpaceString());
747             } else {
748                 RefPtr<Node> node = document()->createEditingTextNode(nonBreakingSpaceString());
749                 insertNodeBeforeAndUpdateNodesInserted(node.get(), m_firstNodeInserted.get());
750             }
751         }
752     }
753     
754     Position lastPositionToSelect;
755
756     // step 4 : handle trailing newline
757     if (m_fragment.hasInterchangeNewlineAtEnd()) {
758         removeLinePlaceholderIfNeeded(linePlaceholder);
759
760         if (!m_lastNodeInserted) {
761             lastPositionToSelect = endingSelection().end().downstream();
762         }
763         else {
764             bool insertParagraph = false;
765             VisiblePosition pos(insertionPos, VP_DEFAULT_AFFINITY);
766             
767             if (startBlock == endBlock && !m_fragment.isBlockFlow(m_lastTopNodeInserted.get())) {
768                 insertParagraph = true;
769             } else {
770                 // Handle end-of-document case.
771                 updateLayout();
772                 if (isEndOfDocument(pos))
773                     insertParagraph = true;
774             }
775             if (insertParagraph) {
776                 setEndingSelection(insertionPos, DOWNSTREAM);
777                 insertParagraphSeparator();
778                 VisiblePosition next = pos.next();
779
780                 // Select up to the paragraph separator that was added.
781                 lastPositionToSelect = next.deepEquivalent().downstream();
782                 updateNodesInserted(lastPositionToSelect.node());
783             } else {
784                 // Select up to the preexising paragraph separator.
785                 VisiblePosition next = pos.next();
786                 lastPositionToSelect = next.deepEquivalent().downstream();
787             }
788         }
789     } else {
790         if (m_lastNodeInserted && m_lastNodeInserted->hasTagName(brTag) && !document()->inStrictMode()) {
791             updateLayout();
792             VisiblePosition pos(Position(m_lastNodeInserted.get(), 1), DOWNSTREAM);
793             if (isEndOfBlock(pos)) {
794                 Node *next = m_lastNodeInserted->traverseNextNode();
795                 bool hasTrailingBR = next && next->hasTagName(brTag) && m_lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
796                 if (!hasTrailingBR) {
797                     // Insert an "extra" BR at the end of the block. 
798                     insertNodeBefore(createBreakElement(document()).get(), m_lastNodeInserted.get());
799                 }
800             }
801         }
802
803         if (beyondEndNode) {
804             updateLayout();
805             RenderingInfoMap renderingInfo;
806             NodeVector nodes;
807             Node* node = beyondEndNode->enclosingInlineElement();
808             Node* refNode = m_lastNodeInserted.get();
809             
810             while (node) {
811                 if (node->isBlockFlowOrBlockTable())
812                     break;
813                     
814                 Node *next = node->nextSibling();
815                 nodes.append(node);
816                 renderingInfo.add(node, new RenderingInfo(styleForNode(node)));
817                 removeNodeAndPruneAncestors(node);
818                 // No need to update inserted node variables.
819                 insertNodeAfter(node, refNode);
820                 refNode = node;
821                 // We want to move the first BR we see, so check for that here.
822                 if (node->hasTagName(brTag))
823                     break;
824                 node = next;
825             }
826
827             fixupNodeStyles(nodes, renderingInfo);
828         }
829     }
830     
831     if (!m_matchStyle)
832         fixupNodeStyles(m_fragment.nodes(), m_fragment.renderingInfo());
833     completeHTMLReplacement(lastPositionToSelect);
834     
835     // step 5 : mop up
836     removeLinePlaceholderIfNeeded(linePlaceholder);
837 }
838
839 void ReplaceSelectionCommand::removeLinePlaceholderIfNeeded(Node *linePlaceholder)
840 {
841     if (!linePlaceholder)
842         return;
843         
844     updateLayout();
845     if (linePlaceholder->inDocument()) {
846         VisiblePosition placeholderPos(linePlaceholder, linePlaceholder->renderer()->caretMinOffset(), DOWNSTREAM);
847         if (placeholderPos.next().isNull() ||
848             !(isStartOfLine(placeholderPos) && isEndOfLine(placeholderPos))) {
849             
850             removeNodeAndPruneAncestors(linePlaceholder);
851         }
852     }
853 }
854
855 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
856 {
857     Position start;
858     Position end;
859
860     if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
861         // Find the last leaf.
862         Node *lastLeaf = m_lastNodeInserted.get();
863         while (1) {
864             Node *nextChild = lastLeaf->lastChild();
865             if (!nextChild)
866                 break;
867             lastLeaf = nextChild;
868         }
869     
870         // Find the first leaf.
871         Node *firstLeaf = m_firstNodeInserted.get();
872         while (1) {
873             Node *nextChild = firstLeaf->firstChild();
874             if (!nextChild)
875                 break;
876             firstLeaf = nextChild;
877         }
878         
879         // Call updateLayout so caretMinOffset and caretMaxOffset return correct values.
880         updateLayout();
881         start = Position(firstLeaf, firstLeaf->caretMinOffset());
882         end = Position(lastLeaf, lastLeaf->caretMaxOffset());
883
884         if (m_matchStyle) {
885             assert(m_insertionStyle);
886             applyStyle(m_insertionStyle.get(), start, end);
887         }    
888         
889         if (lastPositionToSelect.isNotNull())
890             end = lastPositionToSelect;
891     } else if (lastPositionToSelect.isNotNull())
892         start = end = lastPositionToSelect;
893     else
894         return;
895     
896     if (m_selectReplacement)
897         setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
898     else
899         setEndingSelection(end, SEL_DEFAULT_AFFINITY);
900     
901     rebalanceWhitespace();
902 }
903
904 EditAction ReplaceSelectionCommand::editingAction() const
905 {
906     return EditActionPaste;
907 }
908
909 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
910 {
911     insertNodeAfter(insertChild, refChild);
912     updateNodesInserted(insertChild);
913 }
914
915 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, Node *refChild, int offset)
916 {
917     insertNodeAt(insertChild, refChild, offset);
918     updateNodesInserted(insertChild);
919 }
920
921 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(Node *insertChild, Node *refChild)
922 {
923     insertNodeBefore(insertChild, refChild);
924     updateNodesInserted(insertChild);
925 }
926
927 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
928 {
929     if (!node)
930         return;
931
932     m_lastTopNodeInserted = node;
933     if (!m_firstNodeInserted)
934         m_firstNodeInserted = node;
935     
936     if (node == m_lastNodeInserted)
937         return;
938     
939     m_lastNodeInserted = node->lastDescendant();
940 }
941
942 } // namespace WebCore