2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebCore / editing / ReplaceSelectionCommand.cpp
blob4304af31b2daf34c6a7361a2ee0374c077259ca5
1 /*
2 * Copyright (C) 2005, 2006, 2008 Apple 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
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.
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.
26 #include "config.h"
27 #include "ReplaceSelectionCommand.h"
29 #include "ApplyStyleCommand.h"
30 #include "BeforeTextInsertedEvent.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSProperty.h"
33 #include "CSSPropertyNames.h"
34 #include "CSSValueKeywords.h"
35 #include "Document.h"
36 #include "DocumentFragment.h"
37 #include "EditingText.h"
38 #include "EventNames.h"
39 #include "Element.h"
40 #include "Frame.h"
41 #include "HTMLElement.h"
42 #include "HTMLInterchange.h"
43 #include "HTMLInputElement.h"
44 #include "HTMLNames.h"
45 #include "SelectionController.h"
46 #include "SmartReplace.h"
47 #include "TextIterator.h"
48 #include "htmlediting.h"
49 #include "markup.h"
50 #include "visible_units.h"
52 namespace WebCore {
54 using namespace HTMLNames;
56 enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
58 // --- ReplacementFragment helper class
60 class ReplacementFragment : Noncopyable {
61 public:
62 ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const Selection&);
64 Node* firstChild() const;
65 Node* lastChild() const;
67 bool isEmpty() const;
69 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
70 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
72 void removeNode(PassRefPtr<Node>);
73 void removeNodePreservingChildren(Node*);
75 private:
76 PassRefPtr<Node> insertFragmentForTestRendering(Node* context);
77 void removeUnrenderedNodes(Node*);
78 void restoreTestRenderingNodesToFragment(Node*);
79 void removeInterchangeNodes(Node*);
81 void insertNodeBefore(Node* node, Node* refNode);
83 RefPtr<Document> m_document;
84 RefPtr<DocumentFragment> m_fragment;
85 bool m_matchStyle;
86 bool m_hasInterchangeNewlineAtStart;
87 bool m_hasInterchangeNewlineAtEnd;
90 static bool isInterchangeNewlineNode(const Node *node)
92 static String interchangeNewlineClassString(AppleInterchangeNewline);
93 return node && node->hasTagName(brTag) &&
94 static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
97 static bool isInterchangeConvertedSpaceSpan(const Node *node)
99 static String convertedSpaceSpanClassString(AppleConvertedSpace);
100 return node->isHTMLElement() &&
101 static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
104 ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
105 : m_document(document),
106 m_fragment(fragment),
107 m_matchStyle(matchStyle),
108 m_hasInterchangeNewlineAtStart(false),
109 m_hasInterchangeNewlineAtEnd(false)
111 if (!m_document)
112 return;
113 if (!m_fragment)
114 return;
115 if (!m_fragment->firstChild())
116 return;
118 Element* editableRoot = selection.rootEditableElement();
119 ASSERT(editableRoot);
120 if (!editableRoot)
121 return;
123 Node* shadowAncestorNode = editableRoot->shadowAncestorNode();
125 if (!editableRoot->inlineEventListenerForType(eventNames().webkitBeforeTextInsertedEvent) &&
126 // FIXME: Remove these checks once textareas and textfields actually register an event handler.
127 !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextField()) &&
128 !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextArea()) &&
129 editableRoot->isContentRichlyEditable()) {
130 removeInterchangeNodes(m_fragment->firstChild());
131 return;
134 Node* styleNode = selection.base().node();
135 RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
137 RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange();
138 String text = plainText(range.get());
139 // Give the root a chance to change the text.
140 RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
141 ExceptionCode ec = 0;
142 editableRoot->dispatchEvent(evt, ec);
143 ASSERT(ec == 0);
144 if (text != evt->text() || !editableRoot->isContentRichlyEditable()) {
145 restoreTestRenderingNodesToFragment(holder.get());
146 removeNode(holder);
148 m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
149 if (!m_fragment->firstChild())
150 return;
151 holder = insertFragmentForTestRendering(styleNode);
154 removeInterchangeNodes(holder->firstChild());
156 removeUnrenderedNodes(holder.get());
157 restoreTestRenderingNodesToFragment(holder.get());
158 removeNode(holder);
161 bool ReplacementFragment::isEmpty() const
163 return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
166 Node *ReplacementFragment::firstChild() const
168 return m_fragment ? m_fragment->firstChild() : 0;
171 Node *ReplacementFragment::lastChild() const
173 return m_fragment ? m_fragment->lastChild() : 0;
176 void ReplacementFragment::removeNodePreservingChildren(Node *node)
178 if (!node)
179 return;
181 while (RefPtr<Node> n = node->firstChild()) {
182 removeNode(n);
183 insertNodeBefore(n.get(), node);
185 removeNode(node);
188 void ReplacementFragment::removeNode(PassRefPtr<Node> node)
190 if (!node)
191 return;
193 Node *parent = node->parentNode();
194 if (!parent)
195 return;
197 ExceptionCode ec = 0;
198 parent->removeChild(node.get(), ec);
199 ASSERT(ec == 0);
202 void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
204 if (!node || !refNode)
205 return;
207 Node *parent = refNode->parentNode();
208 if (!parent)
209 return;
211 ExceptionCode ec = 0;
212 parent->insertBefore(node, refNode, ec);
213 ASSERT(ec == 0);
216 PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context)
218 Node* body = m_document->body();
219 if (!body)
220 return 0;
222 RefPtr<StyledElement> holder = static_pointer_cast<StyledElement>(createDefaultParagraphElement(m_document.get()));
224 ExceptionCode ec = 0;
226 // Copy the whitespace and user-select style from the context onto this element.
227 // FIXME: We should examine other style properties to see if they would be appropriate to consider during the test rendering.
228 Node* n = context;
229 while (n && !n->isElementNode())
230 n = n->parentNode();
231 if (n) {
232 RefPtr<CSSComputedStyleDeclaration> conFontStyle = computedStyle(n);
233 CSSStyleDeclaration* style = holder->style();
234 style->setProperty(CSSPropertyWhiteSpace, conFontStyle->getPropertyValue(CSSPropertyWhiteSpace), false, ec);
235 ASSERT(ec == 0);
236 style->setProperty(CSSPropertyWebkitUserSelect, conFontStyle->getPropertyValue(CSSPropertyWebkitUserSelect), false, ec);
237 ASSERT(ec == 0);
240 holder->appendChild(m_fragment, ec);
241 ASSERT(ec == 0);
243 body->appendChild(holder.get(), ec);
244 ASSERT(ec == 0);
246 m_document->updateLayoutIgnorePendingStylesheets();
248 return holder.release();
251 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder)
253 if (!holder)
254 return;
256 ExceptionCode ec = 0;
257 while (RefPtr<Node> node = holder->firstChild()) {
258 holder->removeChild(node.get(), ec);
259 ASSERT(ec == 0);
260 m_fragment->appendChild(node.get(), ec);
261 ASSERT(ec == 0);
265 void ReplacementFragment::removeUnrenderedNodes(Node* holder)
267 Vector<Node*> unrendered;
269 for (Node* node = holder->firstChild(); node; node = node->traverseNextNode(holder))
270 if (!isNodeRendered(node) && !isTableStructureNode(node))
271 unrendered.append(node);
273 size_t n = unrendered.size();
274 for (size_t i = 0; i < n; ++i)
275 removeNode(unrendered[i]);
278 void ReplacementFragment::removeInterchangeNodes(Node* startNode)
280 Node* node = startNode;
281 Node* newlineAtStartNode = 0;
282 Node* newlineAtEndNode = 0;
283 while (node) {
284 Node *next = node->traverseNextNode();
285 if (isInterchangeNewlineNode(node)) {
286 if (next || node == startNode) {
287 m_hasInterchangeNewlineAtStart = true;
288 newlineAtStartNode = node;
290 else {
291 m_hasInterchangeNewlineAtEnd = true;
292 newlineAtEndNode = node;
295 else if (isInterchangeConvertedSpaceSpan(node)) {
296 RefPtr<Node> n = 0;
297 while ((n = node->firstChild())) {
298 removeNode(n);
299 insertNodeBefore(n.get(), node);
301 removeNode(node);
302 if (n)
303 next = n->traverseNextNode();
305 node = next;
308 if (newlineAtStartNode)
309 removeNode(newlineAtStartNode);
310 if (newlineAtEndNode)
311 removeNode(newlineAtEndNode);
314 ReplaceSelectionCommand::ReplaceSelectionCommand(Document* document, PassRefPtr<DocumentFragment> fragment,
315 bool selectReplacement, bool smartReplace, bool matchStyle, bool preventNesting, bool movingParagraph,
316 EditAction editAction)
317 : CompositeEditCommand(document),
318 m_selectReplacement(selectReplacement),
319 m_smartReplace(smartReplace),
320 m_matchStyle(matchStyle),
321 m_documentFragment(fragment),
322 m_preventNesting(preventNesting),
323 m_movingParagraph(movingParagraph),
324 m_editAction(editAction)
328 bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart)
330 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
331 VisiblePosition prev = startOfInsertedContent.previous(true);
332 if (prev.isNull())
333 return false;
335 return !selectionStartWasStartOfParagraph &&
336 !fragmentHasInterchangeNewlineAtStart &&
337 isStartOfParagraph(startOfInsertedContent) &&
338 !startOfInsertedContent.deepEquivalent().node()->hasTagName(brTag) &&
339 shouldMerge(startOfInsertedContent, prev);
342 bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
344 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
345 VisiblePosition next = endOfInsertedContent.next(true);
346 if (next.isNull())
347 return false;
349 return !selectionEndWasEndOfParagraph &&
350 isEndOfParagraph(endOfInsertedContent) &&
351 !endOfInsertedContent.deepEquivalent().node()->hasTagName(brTag) &&
352 shouldMerge(endOfInsertedContent, next);
355 static bool isMailPasteAsQuotationNode(const Node* node)
357 return node && node->hasTagName(blockquoteTag) && node->isElementNode() && static_cast<const Element*>(node)->getAttribute(classAttr) == ApplePasteAsQuotation;
360 // Wrap CompositeEditCommand::removeNodePreservingChildren() so we can update the nodes we track
361 void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
363 if (m_firstNodeInserted == node)
364 m_firstNodeInserted = node->traverseNextNode();
365 if (m_lastLeafInserted == node)
366 m_lastLeafInserted = node->lastChild() ? node->lastChild() : node->traverseNextSibling();
367 CompositeEditCommand::removeNodePreservingChildren(node);
370 // Wrap CompositeEditCommand::removeNodeAndPruneAncestors() so we can update the nodes we track
371 void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
373 // prepare in case m_firstNodeInserted and/or m_lastLeafInserted get removed
374 // FIXME: shouldn't m_lastLeafInserted be adjusted using traversePreviousNode()?
375 Node* afterFirst = m_firstNodeInserted ? m_firstNodeInserted->traverseNextSibling() : 0;
376 Node* afterLast = m_lastLeafInserted ? m_lastLeafInserted->traverseNextSibling() : 0;
378 CompositeEditCommand::removeNodeAndPruneAncestors(node);
380 // adjust m_firstNodeInserted and m_lastLeafInserted since either or both may have been removed
381 if (m_lastLeafInserted && !m_lastLeafInserted->inDocument())
382 m_lastLeafInserted = afterLast;
383 if (m_firstNodeInserted && !m_firstNodeInserted->inDocument())
384 m_firstNodeInserted = m_lastLeafInserted && m_lastLeafInserted->inDocument() ? afterFirst : 0;
387 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& from, const VisiblePosition& to)
389 if (from.isNull() || to.isNull())
390 return false;
392 Node* fromNode = from.deepEquivalent().node();
393 Node* toNode = to.deepEquivalent().node();
394 Node* fromNodeBlock = enclosingBlock(fromNode);
395 return !enclosingNodeOfType(from.deepEquivalent(), &isMailPasteAsQuotationNode) &&
396 fromNodeBlock && (!fromNodeBlock->hasTagName(blockquoteTag) || isMailBlockquote(fromNodeBlock)) &&
397 enclosingListChild(fromNode) == enclosingListChild(toNode) &&
398 enclosingTableCell(from.deepEquivalent()) == enclosingTableCell(from.deepEquivalent()) &&
399 // Don't merge to or from a position before or after a block because it would
400 // be a no-op and cause infinite recursion.
401 !isBlock(fromNode) && !isBlock(toNode);
404 // Style rules that match just inserted elements could change their appearance, like
405 // a div inserted into a document with div { display:inline; }.
406 void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance()
408 for (RefPtr<Node> node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
409 // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance
410 if (isStyleSpan(node.get())) {
411 HTMLElement* e = static_cast<HTMLElement*>(node.get());
412 // There are other styles that style rules can give to style spans,
413 // but these are the two important ones because they'll prevent
414 // inserted content from appearing in the right paragraph.
415 // FIXME: Hyatt is concerned that selectively using display:inline will give inconsistent
416 // results. We already know one issue because td elements ignore their display property
417 // in quirks mode (which Mail.app is always in). We should look for an alternative.
418 if (isBlock(e))
419 e->getInlineStyleDecl()->setProperty(CSSPropertyDisplay, CSSValueInline);
420 if (e->renderer() && e->renderer()->style()->floating() != FNONE)
421 e->getInlineStyleDecl()->setProperty(CSSPropertyFloat, CSSValueNone);
423 if (node == m_lastLeafInserted)
424 break;
428 void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds()
430 document()->updateLayoutIgnorePendingStylesheets();
431 if (!m_lastLeafInserted->renderer() &&
432 m_lastLeafInserted->isTextNode() &&
433 !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), selectTag) &&
434 !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), scriptTag)) {
435 if (m_firstNodeInserted == m_lastLeafInserted) {
436 removeNode(m_lastLeafInserted.get());
437 m_lastLeafInserted = 0;
438 m_firstNodeInserted = 0;
439 return;
441 RefPtr<Node> previous = m_lastLeafInserted->traversePreviousNode();
442 removeNode(m_lastLeafInserted.get());
443 m_lastLeafInserted = previous;
446 // We don't have to make sure that m_firstNodeInserted isn't inside a select or script element, because
447 // it is a top level node in the fragment and the user can't insert into those elements.
448 if (!m_firstNodeInserted->renderer() &&
449 m_firstNodeInserted->isTextNode()) {
450 if (m_firstNodeInserted == m_lastLeafInserted) {
451 removeNode(m_firstNodeInserted.get());
452 m_firstNodeInserted = 0;
453 m_lastLeafInserted = 0;
454 return;
456 RefPtr<Node> next = m_firstNodeInserted->traverseNextSibling();
457 removeNode(m_firstNodeInserted.get());
458 m_firstNodeInserted = next;
462 void ReplaceSelectionCommand::handlePasteAsQuotationNode()
464 Node* node = m_firstNodeInserted.get();
465 if (isMailPasteAsQuotationNode(node))
466 removeNodeAttribute(static_cast<Element*>(node), classAttr);
469 VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent()
471 Node* lastNode = m_lastLeafInserted.get();
472 Node* enclosingSelect = enclosingNodeWithTag(Position(lastNode, 0), selectTag);
473 if (enclosingSelect)
474 lastNode = enclosingSelect;
475 return VisiblePosition(Position(lastNode, maxDeepOffset(lastNode)));
478 VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent()
480 // Return the inserted content's first VisiblePosition.
481 return VisiblePosition(nextCandidate(positionBeforeNode(m_firstNodeInserted.get())));
484 // Remove style spans before insertion if they are unnecessary. It's faster because we'll
485 // avoid doing a layout.
486 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
488 Node* topNode = fragment.firstChild();
490 // Handling this case is more complicated (see handleStyleSpans) and doesn't receive the optimization.
491 if (isMailPasteAsQuotationNode(topNode))
492 return false;
494 // Either there are no style spans in the fragment or a WebKit client has added content to the fragment
495 // before inserting it. Look for and handle style spans after insertion.
496 if (!isStyleSpan(topNode))
497 return false;
499 Node* sourceDocumentStyleSpan = topNode;
500 RefPtr<Node> copiedRangeStyleSpan = sourceDocumentStyleSpan->firstChild();
502 RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = rangeCompliantEquivalent(insertionPos).computedStyle()->copyInheritableProperties();
503 String styleText = styleAtInsertionPos->cssText();
505 if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) {
506 fragment.removeNodePreservingChildren(sourceDocumentStyleSpan);
507 if (!isStyleSpan(copiedRangeStyleSpan.get()))
508 return true;
511 if (isStyleSpan(copiedRangeStyleSpan.get()) && styleText == static_cast<Element*>(copiedRangeStyleSpan.get())->getAttribute(styleAttr)) {
512 fragment.removeNodePreservingChildren(copiedRangeStyleSpan.get());
513 return true;
516 return false;
519 // At copy time, WebKit wraps copied content in a span that contains the source document's
520 // default styles. If the copied Range inherits any other styles from its ancestors, we put
521 // those styles on a second span.
522 // This function removes redundant styles from those spans, and removes the spans if all their
523 // styles are redundant.
524 // We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>.
525 // We should remove styles from spans that are overridden by all of their children, either here
526 // or at copy time.
527 void ReplaceSelectionCommand::handleStyleSpans()
529 Node* sourceDocumentStyleSpan = 0;
530 Node* copiedRangeStyleSpan = 0;
531 // The style span that contains the source document's default style should be at
532 // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
533 // so search for the top level style span instead of assuming it's at the top.
534 for (Node* node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
535 if (isStyleSpan(node)) {
536 sourceDocumentStyleSpan = node;
537 // If the copied Range's common ancestor had user applied inheritable styles
538 // on it, they'll be on a second style span, just below the one that holds the
539 // document defaults.
540 if (isStyleSpan(node->firstChild()))
541 copiedRangeStyleSpan = node->firstChild();
542 break;
546 // There might not be any style spans if we're pasting from another application or if
547 // we are here because of a document.execCommand("InsertHTML", ...) call.
548 if (!sourceDocumentStyleSpan)
549 return;
551 RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy();
552 Node* context = sourceDocumentStyleSpan->parentNode();
554 // If Mail wraps the fragment with a Paste as Quotation blockquote, styles from that element are
555 // allowed to override those from the source document, see <rdar://problem/4930986>.
556 if (isMailPasteAsQuotationNode(context)) {
557 RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(context)->copyInheritableProperties();
558 RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(context->parentNode())->copyInheritableProperties();
559 parentStyle->diff(blockquoteStyle.get());
561 DeprecatedValueListConstIterator<CSSProperty> end;
562 for (DeprecatedValueListConstIterator<CSSProperty> it = blockquoteStyle->valuesIterator(); it != end; ++it) {
563 const CSSProperty& property = *it;
564 sourceDocumentStyle->removeProperty(property.id());
567 context = context->parentNode();
570 RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties();
571 contextStyle->diff(sourceDocumentStyle.get());
573 // Remove block properties in the span's style. This prevents properties that probably have no effect
574 // currently from affecting blocks later if the style is cloned for a new block element during a future
575 // editing operation.
576 // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked
577 // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might.
578 sourceDocumentStyle->removeBlockProperties();
580 // The styles on sourceDocumentStyleSpan are all redundant, and there is no copiedRangeStyleSpan
581 // to consider. We're finished.
582 if (sourceDocumentStyle->length() == 0 && !copiedRangeStyleSpan) {
583 removeNodePreservingChildren(sourceDocumentStyleSpan);
584 return;
587 // There are non-redundant styles on sourceDocumentStyleSpan, but there is no
588 // copiedRangeStyleSpan. Clear the redundant styles from sourceDocumentStyleSpan
589 // and return.
590 if (sourceDocumentStyle->length() > 0 && !copiedRangeStyleSpan) {
591 setNodeAttribute(static_cast<Element*>(sourceDocumentStyleSpan), styleAttr, sourceDocumentStyle->cssText());
592 return;
595 RefPtr<CSSMutableStyleDeclaration> copiedRangeStyle = static_cast<HTMLElement*>(copiedRangeStyleSpan)->getInlineStyleDecl()->copy();
597 // We're going to put sourceDocumentStyleSpan's non-redundant styles onto copiedRangeStyleSpan,
598 // as long as they aren't overridden by ones on copiedRangeStyleSpan.
599 sourceDocumentStyle->merge(copiedRangeStyle.get(), true);
600 copiedRangeStyle = sourceDocumentStyle;
602 removeNodePreservingChildren(sourceDocumentStyleSpan);
604 // Remove redundant styles.
605 context = copiedRangeStyleSpan->parentNode();
606 contextStyle = computedStyle(context)->copyInheritableProperties();
607 contextStyle->diff(copiedRangeStyle.get());
609 // See the comments above about removing block properties.
610 copiedRangeStyle->removeBlockProperties();
612 // All the styles on copiedRangeStyleSpan are redundant, remove it.
613 if (copiedRangeStyle->length() == 0) {
614 removeNodePreservingChildren(copiedRangeStyleSpan);
615 return;
618 // Clear the redundant styles from the span's style attribute.
619 // FIXME: If font-family:-webkit-monospace is non-redundant, then the font-size should stay, even if it
620 // appears redundant.
621 setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->cssText());
624 void ReplaceSelectionCommand::doApply()
626 Selection selection = endingSelection();
627 ASSERT(selection.isCaretOrRange());
628 ASSERT(selection.start().node());
629 if (selection.isNone() || !selection.start().node())
630 return;
632 bool selectionIsPlainText = !selection.isContentRichlyEditable();
634 Element* currentRoot = selection.rootEditableElement();
635 ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
637 if (m_matchStyle)
638 m_insertionStyle = styleAtPosition(selection.start());
640 VisiblePosition visibleStart = selection.visibleStart();
641 VisiblePosition visibleEnd = selection.visibleEnd();
643 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
644 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
646 Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().node());
648 if (selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph ||
649 startBlock == currentRoot ||
650 startBlock && startBlock->renderer() && startBlock->renderer()->isListItem() ||
651 selectionIsPlainText)
652 m_preventNesting = false;
654 Position insertionPos = selection.start();
656 if (selection.isRange()) {
657 // When the end of the selection being pasted into is at the end of a paragraph, and that selection
658 // spans multiple blocks, not merging may leave an empty line.
659 // When the start of the selection being pasted into is at the start of a block, not merging
660 // will leave hanging block(s).
661 bool mergeBlocksAfterDelete = isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
662 // FIXME: We should only expand to include fully selected special elements if we are copying a
663 // selection and pasting it on top of itself.
664 deleteSelection(false, mergeBlocksAfterDelete, true, false);
665 visibleStart = endingSelection().visibleStart();
666 if (fragment.hasInterchangeNewlineAtStart()) {
667 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
668 if (!isEndOfDocument(visibleStart))
669 setEndingSelection(visibleStart.next());
670 } else
671 insertParagraphSeparator();
673 insertionPos = endingSelection().start();
674 } else {
675 ASSERT(selection.isCaret());
676 if (fragment.hasInterchangeNewlineAtStart()) {
677 VisiblePosition next = visibleStart.next(true);
678 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull())
679 setEndingSelection(next);
680 else
681 insertParagraphSeparator();
683 // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block.
684 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret.
685 // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>,
686 // not <div>xbar<div>bar</div><div>bazx</div></div>
687 if (m_preventNesting && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
688 insertParagraphSeparator();
689 setEndingSelection(endingSelection().visibleStart().previous());
691 insertionPos = endingSelection().start();
694 // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world.
695 prepareWhitespaceAtPositionForSplit(insertionPos);
697 // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after
698 // 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
699 // away, there are positions after the br which map to the same visible position as [br, 0]).
700 Node* endBR = insertionPos.downstream().node()->hasTagName(brTag) ? insertionPos.downstream().node() : 0;
701 VisiblePosition originalVisPosBeforeEndBR;
702 if (endBR)
703 originalVisPosBeforeEndBR = VisiblePosition(endBR, 0, DOWNSTREAM).previous();
705 startBlock = enclosingBlock(insertionPos.node());
707 // Adjust insertionPos to prevent nesting.
708 if (m_preventNesting && startBlock) {
709 ASSERT(startBlock != currentRoot);
710 VisiblePosition visibleInsertionPos(insertionPos);
711 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd()))
712 insertionPos = positionAfterNode(startBlock);
713 else if (isStartOfBlock(visibleInsertionPos))
714 insertionPos = positionBeforeNode(startBlock);
717 // Paste into run of tabs splits the tab span.
718 insertionPos = positionOutsideTabSpan(insertionPos);
720 // Paste at start or end of link goes outside of link.
721 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
723 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
724 // any work performed after this that queries or uses the typing style.
725 if (Frame* frame = document()->frame())
726 frame->clearTypingStyle();
728 bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
730 // We're finished if there is nothing to add.
731 if (fragment.isEmpty() || !fragment.firstChild())
732 return;
734 // 1) Insert the content.
735 // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>.
736 // 3) Merge the start of the added content with the content before the position being pasted into.
737 // 4) Do one of the following: a) expand the last br if the fragment ends with one and it collapsed,
738 // b) merge the last paragraph of the incoming fragment with the paragraph that contained the
739 // end of the selection that was pasted into, or c) handle an interchange newline at the end of the
740 // incoming fragment.
741 // 5) Add spaces for smart replace.
742 // 6) Select the replacement if requested, and match style if requested.
744 VisiblePosition startOfInsertedContent, endOfInsertedContent;
746 RefPtr<Node> refNode = fragment.firstChild();
747 RefPtr<Node> node = refNode->nextSibling();
749 fragment.removeNode(refNode);
750 insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos);
752 while (node) {
753 Node* next = node->nextSibling();
754 fragment.removeNode(node);
755 insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
756 refNode = node;
757 node = next;
760 removeUnrenderedTextNodesAtEnds();
762 negateStyleRulesThatAffectAppearance();
764 if (!handledStyleSpans)
765 handleStyleSpans();
767 // Mutation events (bug 20161) may have already removed the inserted content
768 if (!m_firstNodeInserted || !m_firstNodeInserted->inDocument())
769 return;
771 endOfInsertedContent = positionAtEndOfInsertedContent();
772 startOfInsertedContent = positionAtStartOfInsertedContent();
774 // We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
775 // didn't have a br after it, so the inserted content ended up in the same paragraph.
776 if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.offset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
777 insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
779 Position lastPositionToSelect;
781 bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
783 if (shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR))
784 removeNodeAndPruneAncestors(endBR);
786 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart())) {
787 // Bail to avoid infinite recursion.
788 if (m_movingParagraph) {
789 // setting display:inline does not work for td elements in quirks mode
790 ASSERT(m_firstNodeInserted->hasTagName(tdTag));
791 return;
793 VisiblePosition destination = startOfInsertedContent.previous();
794 VisiblePosition startOfParagraphToMove = startOfInsertedContent;
796 // Merging the the first paragraph of inserted content with the content that came
797 // before the selection that was pasted into would also move content after
798 // the selection that was pasted into if: only one paragraph was being pasted,
799 // and it was not wrapped in a block, the selection that was pasted into ended
800 // at the end of a block and the next paragraph didn't start at the start of a block.
801 // Insert a line break just after the inserted content to separate it from what
802 // comes after and prevent that from happening.
803 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
804 if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove)
805 insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent.deepEquivalent());
807 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
808 // only ever used to create positions where inserted content starts/ends.
809 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
810 m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().downstream().node();
811 if (!m_lastLeafInserted->inDocument())
812 m_lastLeafInserted = endingSelection().visibleEnd().deepEquivalent().upstream().node();
815 endOfInsertedContent = positionAtEndOfInsertedContent();
816 startOfInsertedContent = positionAtStartOfInsertedContent();
818 if (interchangeNewlineAtEnd) {
819 VisiblePosition next = endOfInsertedContent.next(true);
821 if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) {
822 if (!isStartOfParagraph(endOfInsertedContent)) {
823 setEndingSelection(endOfInsertedContent);
824 // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph
825 // block's style seems to annoy users.
826 insertParagraphSeparator(true);
828 // Select up to the paragraph separator that was added.
829 lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
830 updateNodesInserted(lastPositionToSelect.node());
832 } else {
833 // Select up to the beginning of the next paragraph.
834 lastPositionToSelect = next.deepEquivalent().downstream();
837 } else if (shouldMergeEnd(selectionEndWasEndOfParagraph)) {
838 // Bail to avoid infinite recursion.
839 if (m_movingParagraph) {
840 ASSERT_NOT_REACHED();
841 return;
843 // Merging two paragraphs will destroy the moved one's block styles. Always move forward to preserve
844 // the block style of the paragraph already in the document, unless the paragraph to move would include the
845 // what was the start of the selection that was pasted into.
846 bool mergeForward = !inSameParagraph(startOfInsertedContent, endOfInsertedContent) || isStartOfParagraph(startOfInsertedContent);
848 VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
849 VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
851 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
852 // Merging forward will remove m_lastLeafInserted from the document.
853 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
854 // only ever used to create positions where inserted content starts/ends.
855 if (mergeForward) {
856 m_lastLeafInserted = destination.previous().deepEquivalent().node();
857 if (!m_firstNodeInserted->inDocument())
858 m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().node();
862 handlePasteAsQuotationNode();
864 endOfInsertedContent = positionAtEndOfInsertedContent();
865 startOfInsertedContent = positionAtStartOfInsertedContent();
867 // Add spaces for smart replace.
868 if (m_smartReplace && currentRoot) {
869 // Disable smart replace for password fields.
870 Node* start = currentRoot->shadowAncestorNode();
871 if (start->hasTagName(inputTag) && static_cast<HTMLInputElement*>(start)->inputType() == HTMLInputElement::PASSWORD)
872 m_smartReplace = false;
874 if (m_smartReplace) {
875 bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
876 !isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
877 if (needsTrailingSpace) {
878 RenderObject* renderer = m_lastLeafInserted->renderer();
879 bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
880 Node* endNode = positionAtEndOfInsertedContent().deepEquivalent().upstream().node();
881 if (endNode->isTextNode()) {
882 Text* text = static_cast<Text*>(endNode);
883 insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
884 } else {
885 RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
886 insertNodeAfterAndUpdateNodesInserted(node.get(), endNode);
890 bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
891 !isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
892 if (needsLeadingSpace) {
893 RenderObject* renderer = m_lastLeafInserted->renderer();
894 bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
895 Node* startNode = positionAtStartOfInsertedContent().deepEquivalent().downstream().node();
896 if (startNode->isTextNode()) {
897 Text* text = static_cast<Text*>(startNode);
898 insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
899 } else {
900 RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
901 // Don't updateNodesInserted. Doing so would set m_lastLeafInserted to be the node containing the
902 // leading space, but m_lastLeafInserted is supposed to mark the end of pasted content.
903 insertNodeBefore(node.get(), startNode);
904 // FIXME: Use positions to track the start/end of inserted content.
905 m_firstNodeInserted = node;
910 completeHTMLReplacement(lastPositionToSelect);
913 bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR)
915 if (!endBR || !endBR->inDocument())
916 return false;
918 VisiblePosition visiblePos(Position(endBR, 0));
920 // Don't remove the br if nothing was inserted.
921 if (visiblePos.previous() == originalVisPosBeforeEndBR)
922 return false;
924 // Remove the br if it is collapsed away and so is unnecessary.
925 if (!document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
926 return true;
928 // A br that was originally holding a line open should be displaced by inserted content or turned into a line break.
929 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
930 return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
933 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
935 Position start;
936 Position end;
938 // FIXME: This should never not be the case.
939 if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastLeafInserted && m_lastLeafInserted->inDocument()) {
941 start = positionAtStartOfInsertedContent().deepEquivalent();
942 end = positionAtEndOfInsertedContent().deepEquivalent();
944 // FIXME (11475): Remove this and require that the creator of the fragment to use nbsps.
945 rebalanceWhitespaceAt(start);
946 rebalanceWhitespaceAt(end);
948 if (m_matchStyle) {
949 ASSERT(m_insertionStyle);
950 applyStyle(m_insertionStyle.get(), start, end);
953 if (lastPositionToSelect.isNotNull())
954 end = lastPositionToSelect;
955 } else if (lastPositionToSelect.isNotNull())
956 start = end = lastPositionToSelect;
957 else
958 return;
960 if (m_selectReplacement)
961 setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
962 else
963 setEndingSelection(Selection(end, SEL_DEFAULT_AFFINITY));
966 EditAction ReplaceSelectionCommand::editingAction() const
968 return m_editAction;
971 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
973 insertNodeAfter(insertChild, refChild);
974 updateNodesInserted(insertChild);
977 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, const Position& p)
979 insertNodeAt(insertChild, p);
980 updateNodesInserted(insertChild);
983 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(Node *insertChild, Node *refChild)
985 insertNodeBefore(insertChild, refChild);
986 updateNodesInserted(insertChild);
989 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
991 if (!node)
992 return;
994 if (!m_firstNodeInserted)
995 m_firstNodeInserted = node;
997 if (node == m_lastLeafInserted)
998 return;
1000 m_lastLeafInserted = node->lastDescendant();
1003 } // namespace WebCore