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
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 "CSSProperty.h"
33 #include "CSSPropertyNames.h"
34 #include "CSSValueKeywords.h"
36 #include "DocumentFragment.h"
37 #include "EditingText.h"
38 #include "EventNames.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"
50 #include "visible_units.h"
54 using namespace HTMLNames
;
56 enum EFragmentType
{ EmptyFragment
, SingleTextNodeFragment
, TreeFragment
};
58 // --- ReplacementFragment helper class
60 class ReplacementFragment
: Noncopyable
{
62 ReplacementFragment(Document
*, DocumentFragment
*, bool matchStyle
, const Selection
&);
64 Node
* firstChild() const;
65 Node
* lastChild() const;
69 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart
; }
70 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd
; }
72 void removeNode(PassRefPtr
<Node
>);
73 void removeNodePreservingChildren(Node
*);
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
;
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)
115 if (!m_fragment
->firstChild())
118 Element
* editableRoot
= selection
.rootEditableElement();
119 ASSERT(editableRoot
);
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());
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
);
144 if (text
!= evt
->text() || !editableRoot
->isContentRichlyEditable()) {
145 restoreTestRenderingNodesToFragment(holder
.get());
148 m_fragment
= createFragmentFromText(selection
.toRange().get(), evt
->text());
149 if (!m_fragment
->firstChild())
151 holder
= insertFragmentForTestRendering(styleNode
);
154 removeInterchangeNodes(holder
->firstChild());
156 removeUnrenderedNodes(holder
.get());
157 restoreTestRenderingNodesToFragment(holder
.get());
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
)
181 while (RefPtr
<Node
> n
= node
->firstChild()) {
183 insertNodeBefore(n
.get(), node
);
188 void ReplacementFragment::removeNode(PassRefPtr
<Node
> node
)
193 Node
*parent
= node
->parentNode();
197 ExceptionCode ec
= 0;
198 parent
->removeChild(node
.get(), ec
);
202 void ReplacementFragment::insertNodeBefore(Node
*node
, Node
*refNode
)
204 if (!node
|| !refNode
)
207 Node
*parent
= refNode
->parentNode();
211 ExceptionCode ec
= 0;
212 parent
->insertBefore(node
, refNode
, ec
);
216 PassRefPtr
<Node
> ReplacementFragment::insertFragmentForTestRendering(Node
* context
)
218 Node
* body
= m_document
->body();
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.
229 while (n
&& !n
->isElementNode())
232 RefPtr
<CSSComputedStyleDeclaration
> conFontStyle
= computedStyle(n
);
233 CSSStyleDeclaration
* style
= holder
->style();
234 style
->setProperty(CSSPropertyWhiteSpace
, conFontStyle
->getPropertyValue(CSSPropertyWhiteSpace
), false, ec
);
236 style
->setProperty(CSSPropertyWebkitUserSelect
, conFontStyle
->getPropertyValue(CSSPropertyWebkitUserSelect
), false, ec
);
240 holder
->appendChild(m_fragment
, ec
);
243 body
->appendChild(holder
.get(), ec
);
246 m_document
->updateLayoutIgnorePendingStylesheets();
248 return holder
.release();
251 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node
*holder
)
256 ExceptionCode ec
= 0;
257 while (RefPtr
<Node
> node
= holder
->firstChild()) {
258 holder
->removeChild(node
.get(), ec
);
260 m_fragment
->appendChild(node
.get(), ec
);
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;
284 Node
*next
= node
->traverseNextNode();
285 if (isInterchangeNewlineNode(node
)) {
286 if (next
|| node
== startNode
) {
287 m_hasInterchangeNewlineAtStart
= true;
288 newlineAtStartNode
= node
;
291 m_hasInterchangeNewlineAtEnd
= true;
292 newlineAtEndNode
= node
;
295 else if (isInterchangeConvertedSpaceSpan(node
)) {
297 while ((n
= node
->firstChild())) {
299 insertNodeBefore(n
.get(), node
);
303 next
= n
->traverseNextNode();
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);
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);
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())
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.
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
)
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;
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;
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
);
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
))
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
))
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()))
511 if (isStyleSpan(copiedRangeStyleSpan
.get()) && styleText
== static_cast<Element
*>(copiedRangeStyleSpan
.get())->getAttribute(styleAttr
)) {
512 fragment
.removeNodePreservingChildren(copiedRangeStyleSpan
.get());
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
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();
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
)
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
);
587 // There are non-redundant styles on sourceDocumentStyleSpan, but there is no
588 // copiedRangeStyleSpan. Clear the redundant styles from sourceDocumentStyleSpan
590 if (sourceDocumentStyle
->length() > 0 && !copiedRangeStyleSpan
) {
591 setNodeAttribute(static_cast<Element
*>(sourceDocumentStyleSpan
), styleAttr
, sourceDocumentStyle
->cssText());
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
);
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())
632 bool selectionIsPlainText
= !selection
.isContentRichlyEditable();
634 Element
* currentRoot
= selection
.rootEditableElement();
635 ReplacementFragment
fragment(document(), m_documentFragment
.get(), m_matchStyle
, selection
);
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());
671 insertParagraphSeparator();
673 insertionPos
= endingSelection().start();
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
);
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
;
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())
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
);
753 Node
* next
= node
->nextSibling();
754 fragment
.removeNode(node
);
755 insertNodeAfterAndUpdateNodesInserted(node
.get(), refNode
.get());
760 removeUnrenderedTextNodesAtEnds();
762 negateStyleRulesThatAffectAppearance();
764 if (!handledStyleSpans
)
767 // Mutation events (bug 20161) may have already removed the inserted content
768 if (!m_firstNodeInserted
|| !m_firstNodeInserted
->inDocument())
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
));
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());
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();
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.
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() : " ");
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() : " ");
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())
918 VisiblePosition
visiblePos(Position(endBR
, 0));
920 // Don't remove the br if nothing was inserted.
921 if (visiblePos
.previous() == originalVisPosBeforeEndBR
)
924 // Remove the br if it is collapsed away and so is unnecessary.
925 if (!document()->inStrictMode() && isEndOfBlock(visiblePos
) && !isStartOfParagraph(visiblePos
))
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
)
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
);
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
;
960 if (m_selectReplacement
)
961 setEndingSelection(Selection(start
, end
, SEL_DEFAULT_AFFINITY
));
963 setEndingSelection(Selection(end
, SEL_DEFAULT_AFFINITY
));
966 EditAction
ReplaceSelectionCommand::editingAction() const
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
)
994 if (!m_firstNodeInserted
)
995 m_firstNodeInserted
= node
;
997 if (node
== m_lastLeafInserted
)
1000 m_lastLeafInserted
= node
->lastDescendant();
1003 } // namespace WebCore