2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ReplaceSelectionCommand.h"
29 #include "ApplyStyleCommand.h"
30 #include "BeforeTextInsertedEvent.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSPropertyNames.h"
34 #include "DocumentFragment.h"
35 #include "EditingText.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"
45 #include "visible_units.h"
49 using namespace HTMLNames;
51 ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
52 : m_document(document),
54 m_matchStyle(matchStyle),
55 m_hasInterchangeNewlineAtStart(false),
56 m_hasInterchangeNewlineAtEnd(false),
57 m_hasMoreThanOneBlock(false)
63 Node* firstChild = m_fragment->firstChild();
67 Element* editableRoot = selection.rootEditableElement();
72 Node* styleNode = selection.base().node();
73 RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
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);
80 editableRoot->dispatchEvent(evt, ec, true);
82 if (text != evt->text() || !editableRoot->isContentRichlyEditable()) {
83 restoreTestRenderingNodesToFragment(holder.get());
86 m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
87 firstChild = m_fragment->firstChild();
90 holder = insertFragmentForTestRendering(styleNode);
93 Node *node = firstChild;
94 Node *newlineAtStartNode = 0;
95 Node *newlineAtEndNode = 0;
97 Node *next = node->traverseNextNode();
98 if (isInterchangeNewlineNode(node)) {
99 if (next || node == firstChild) {
100 m_hasInterchangeNewlineAtStart = true;
101 newlineAtStartNode = node;
104 m_hasInterchangeNewlineAtEnd = true;
105 newlineAtEndNode = node;
108 else if (isInterchangeConvertedSpaceSpan(node)) {
110 while ((n = node->firstChild())) {
112 insertNodeBefore(n.get(), node);
116 next = n->traverseNextNode();
121 if (newlineAtStartNode)
122 removeNode(newlineAtStartNode);
123 if (newlineAtEndNode)
124 removeNode(newlineAtEndNode);
126 saveRenderingInfo(holder.get());
127 removeUnrenderedNodes(holder.get());
128 m_hasMoreThanOneBlock = renderedBlocks(holder.get()) > 1;
129 restoreTestRenderingNodesToFragment(holder.get());
134 bool ReplacementFragment::isEmpty() const
136 return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
139 Node *ReplacementFragment::firstChild() const
141 return m_fragment->firstChild();
144 Node *ReplacementFragment::lastChild() const
146 return m_fragment->lastChild();
149 static bool isMailPasteAsQuotationNode(const Node *node)
151 return node && static_cast<const Element *>(node)->getAttribute("class") == ApplePasteAsQuotation;
154 Node *ReplacementFragment::mergeStartNode() const
156 Node *node = m_fragment->firstChild();
157 while (node && isBlockFlow(node) && !isMailPasteAsQuotationNode(node))
158 node = node->traverseNextNode();
162 bool ReplacementFragment::isInterchangeNewlineNode(const Node *node)
164 static String interchangeNewlineClassString(AppleInterchangeNewline);
165 return node && node->hasTagName(brTag) &&
166 static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
169 bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const Node *node)
171 static String convertedSpaceSpanClassString(AppleConvertedSpace);
172 return node->isHTMLElement() &&
173 static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
176 Node *ReplacementFragment::enclosingBlock(Node *node) const
178 while (node && !isBlockFlow(node))
179 node = node->parentNode();
180 return node ? node : m_fragment.get();
183 void ReplacementFragment::removeNodePreservingChildren(Node *node)
188 while (RefPtr<Node> n = node->firstChild()) {
190 insertNodeBefore(n.get(), node);
195 void ReplacementFragment::removeNode(PassRefPtr<Node> node)
200 Node *parent = node->parentNode();
204 ExceptionCode ec = 0;
205 parent->removeChild(node.get(), ec);
209 void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
211 if (!node || !refNode)
214 Node *parent = refNode->parentNode();
218 ExceptionCode ec = 0;
219 parent->insertBefore(node, refNode, ec);
223 PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context)
225 Node* body = m_document->body();
229 RefPtr<StyledElement> holder = static_pointer_cast<StyledElement>(createDefaultParagraphElement(m_document.get()));
231 ExceptionCode ec = 0;
233 // Copy the whitespace style from the context onto this element.
235 while (n && !n->isElementNode())
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);
244 holder->appendChild(m_fragment, ec);
247 body->appendChild(holder.get(), ec);
250 m_document->updateLayoutIgnorePendingStylesheets();
252 return holder.release();
255 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder)
260 ExceptionCode ec = 0;
261 while (RefPtr<Node> node = holder->firstChild()) {
262 holder->removeChild(node.get(), ec);
264 m_fragment->appendChild(node.get(), ec);
269 bool ReplacementFragment::isBlockFlow(Node* node) const
271 RefPtr<RenderingInfo> info = m_renderingInfo.get(node);
276 return info->isBlockFlow();
279 static String &matchNearestBlockquoteColorString()
281 static String matchNearestBlockquoteColorString = "match";
282 return matchNearestBlockquoteColorString;
285 void ReplaceSelectionCommand::fixupNodeStyles(const NodeVector& nodes, const RenderingInfoMap& renderingInfo)
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.
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);
299 CSSMutableStyleDeclaration *desiredStyle = info->style();
300 ASSERT(desiredStyle);
302 if (!node->inDocument())
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();
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);
322 desiredStyle->removeProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
324 currentStyle->diff(desiredStyle);
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();
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)));
338 static PassRefPtr<CSSMutableStyleDeclaration> styleForNode(Node *node)
340 if (!node || !node->inDocument())
343 RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(node, 0).computedStyle();
344 RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
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
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));
353 style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
354 return style.release();
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));
361 style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
363 return style.release();
366 void ReplacementFragment::saveRenderingInfo(Node *holder)
368 m_document->updateLayoutIgnorePendingStylesheets();
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()));
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);
382 void ReplacementFragment::removeUnrenderedNodes(Node *holder)
384 DeprecatedPtrList<Node> unrendered;
386 for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
387 if (!isNodeRendered(node) && !isTableStructureNode(node))
388 unrendered.append(node);
391 for (DeprecatedPtrListIterator<Node> it(unrendered); it.current(); ++it)
392 removeNode(it.current());
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)
400 for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
401 if (node->isBlockFlow()) {
407 Node *block = node->enclosingBlockFlowElement();
418 void ReplacementFragment::removeStyleNodes()
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();
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) ||
442 removeNodePreservingChildren(node);
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();
458 RenderingInfo::RenderingInfo(PassRefPtr<CSSMutableStyleDeclaration> style, bool isBlockFlow = false)
459 : m_style(style), m_isBlockFlow(isBlockFlow)
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)
474 ReplaceSelectionCommand::~ReplaceSelectionCommand()
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)
481 if (m_forceMergeStart)
484 VisiblePosition visibleStart = destinationSelection.visibleStart();
485 Node* startBlock = destinationSelection.start().node()->enclosingBlockFlowElement();
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()))
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))
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))
501 if (!incomingFragment.hasInterchangeNewlineAtStart() &&
502 (!isStartOfParagraph(visibleStart) || !incomingFragment.hasInterchangeNewlineAtEnd() && !incomingFragment.hasMoreThanOneBlock()))
508 bool ReplaceSelectionCommand::shouldMergeEnd(const VisiblePosition& endOfInsertedContent, bool selectionEndWasEndOfParagraph)
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);
521 void ReplaceSelectionCommand::doApply()
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())
530 if (!selection.isContentRichlyEditable())
533 Element* currentRoot = selection.rootEditableElement();
534 ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
536 if (fragment.isEmpty())
540 m_insertionStyle = styleAtPosition(selection.start());
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();
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);
550 bool endWasEndOfParagraph = isEndOfParagraph(visibleEnd);
552 Position startPos = selection.start();
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);
563 visibleStart = endingSelection().visibleStart();
564 if (fragment.hasInterchangeNewlineAtStart()) {
565 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
566 if (!isEndOfDocument(visibleStart))
567 setEndingSelection(visibleStart.next());
569 insertParagraphSeparator();
571 startPos = endingSelection().start();
574 ASSERT(selection.isCaret());
575 if (fragment.hasInterchangeNewlineAtStart()) {
576 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
577 if (!isEndOfDocument(visibleStart))
578 setEndingSelection(visibleStart.next());
580 insertParagraphSeparator();
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());
593 startPos = endingSelection().start();
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;
601 if (startAtStartOfBlock && startBlock->inDocument())
602 startPos = Position(startBlock, 0);
604 // paste into run of tabs splits the tab span
605 startPos = positionOutsideTabSpan(startPos);
607 // paste at start or end of link goes outside of link
608 startPos = positionAvoidingSpecialElementBoundary(startPos);
610 Frame *frame = document()->frame();
612 // FIXME: Improve typing style.
613 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
614 frame->clearTypingStyle();
617 // done if there is nothing to add
618 if (!fragment.firstChild())
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.
632 // initially, we say the insertion point is the start of selection
634 Position insertionPos = startPos;
636 // Add the first paragraph.
638 RefPtr<Node> refNode = fragment.mergeStartNode();
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());
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);
660 // update insertion point to be at the end of the last block inserted
661 if (m_lastNodeInserted) {
663 insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
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.
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();
700 Position pos = next.deepEquivalent().downstream();
701 insertNodeAtAndUpdateNodesInserted(refNode.get(), pos.node(), pos.offset());
703 insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos.node(), insertionPos.offset());
707 Node* next = node->nextSibling();
708 fragment.removeNode(node);
709 insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
714 insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
717 Position lastPositionToSelect;
719 bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
720 bool lastNodeInsertedWasBR = m_lastNodeInserted->hasTagName(brTag);
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();
729 removeNodeAndPruneAncestors(endBR);
732 // Styles were removed during the test insertion. Restore them.
734 fixupNodeStyles(fragment.nodes(), fragment.renderingInfo());
736 VisiblePosition endOfInsertedContent(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
737 VisiblePosition startOfInsertedContent(Position(m_firstNodeInserted.get(), 0));
739 if (interchangeNewlineAtEnd) {
740 VisiblePosition pos(insertionPos);
741 VisiblePosition next = pos.next();
743 if (endWasEndOfParagraph || !isEndOfParagraph(pos) || next.rootEditableElement() != currentRoot) {
744 if (!isStartOfParagraph(pos)) {
745 setEndingSelection(pos.deepEquivalent().downstream(), DOWNSTREAM);
746 insertParagraphSeparator();
748 // Select up to the paragraph separator that was added.
749 lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
750 updateNodesInserted(lastPositionToSelect.node());
753 // Select up to the beginning of the next paragraph.
754 lastPositionToSelect = next.deepEquivalent().downstream();
757 } else if (lastNodeInsertedWasBR) {
758 // We want to honor the last incoming line break, so, if it will collapse away because of quirks mode,
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());
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.
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);
779 deleteTextFromNode(textNode, offset, 1);
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);
786 VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
787 VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
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.
794 m_lastNodeInserted = destination.previous().deepEquivalent().node();
798 endOfInsertedContent = VisiblePosition(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
799 startOfInsertedContent = VisiblePosition(Position(m_firstNodeInserted.get(), 0));
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() : " ");
812 RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
813 insertNodeAfterAndUpdateNodesInserted(node.get(), m_lastNodeInserted.get());
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() : " ");
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;
836 completeHTMLReplacement(lastPositionToSelect);
839 bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR)
841 if (!endBR || !endBR->inDocument())
844 VisiblePosition visiblePos(Position(endBR, 0));
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);
854 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
859 if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
861 Node* lastLeaf = m_lastNodeInserted->lastDescendant();
862 Node* firstLeaf = m_firstNodeInserted->firstDescendant();
864 start = Position(firstLeaf, 0);
865 end = Position(lastLeaf, maxDeepOffset(lastLeaf));
867 // FIXME: Should we treat all spaces in incoming content as having been rendered?
868 rebalanceWhitespaceAt(start);
869 rebalanceWhitespaceAt(end);
872 assert(m_insertionStyle);
873 applyStyle(m_insertionStyle.get(), start, end);
876 if (lastPositionToSelect.isNotNull())
877 end = lastPositionToSelect;
878 } else if (lastPositionToSelect.isNotNull())
879 start = end = lastPositionToSelect;
883 if (m_selectReplacement)
884 setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
886 setEndingSelection(end, SEL_DEFAULT_AFFINITY);
889 EditAction ReplaceSelectionCommand::editingAction() const
894 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
896 insertNodeAfter(insertChild, refChild);
897 updateNodesInserted(insertChild);
900 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, Node *refChild, int offset)
902 insertNodeAt(insertChild, refChild, offset);
903 updateNodesInserted(insertChild);
906 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(Node *insertChild, Node *refChild)
908 insertNodeBefore(insertChild, refChild);
909 updateNodesInserted(insertChild);
912 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
917 m_lastTopNodeInserted = node;
918 if (!m_firstNodeInserted)
919 m_firstNodeInserted = node;
921 if (node == m_lastNodeInserted)
924 m_lastNodeInserted = node->lastDescendant();
927 } // namespace WebCore