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 "BreakBlockquoteCommand.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSMutableStyleDeclaration.h"
34 #include "CSSProperty.h"
35 #include "CSSPropertyNames.h"
36 #include "CSSValueKeywords.h"
38 #include "DocumentFragment.h"
39 #include "EditingText.h"
41 #include "EventNames.h"
43 #include "HTMLElement.h"
44 #include "HTMLInputElement.h"
45 #include "HTMLInterchange.h"
46 #include "HTMLNames.h"
47 #include "SelectionController.h"
48 #include "SmartReplace.h"
49 #include "TextIterator.h"
50 #include "htmlediting.h"
52 #include "visible_units.h"
53 #include <wtf/StdLibExtras.h>
57 using namespace HTMLNames
;
59 enum EFragmentType
{ EmptyFragment
, SingleTextNodeFragment
, TreeFragment
};
61 // --- ReplacementFragment helper class
63 class ReplacementFragment
: public Noncopyable
{
65 ReplacementFragment(Document
*, DocumentFragment
*, bool matchStyle
, const VisibleSelection
&);
67 Node
* firstChild() const;
68 Node
* lastChild() const;
72 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart
; }
73 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd
; }
75 void removeNode(PassRefPtr
<Node
>);
76 void removeNodePreservingChildren(Node
*);
79 PassRefPtr
<Node
> insertFragmentForTestRendering(Node
* context
);
80 void removeUnrenderedNodes(Node
*);
81 void restoreTestRenderingNodesToFragment(Node
*);
82 void removeInterchangeNodes(Node
*);
84 void insertNodeBefore(PassRefPtr
<Node
> node
, Node
* refNode
);
86 RefPtr
<Document
> m_document
;
87 RefPtr
<DocumentFragment
> m_fragment
;
89 bool m_hasInterchangeNewlineAtStart
;
90 bool m_hasInterchangeNewlineAtEnd
;
93 static bool isInterchangeNewlineNode(const Node
*node
)
95 DEFINE_STATIC_LOCAL(String
, interchangeNewlineClassString
, (AppleInterchangeNewline
));
96 return node
&& node
->hasTagName(brTag
) &&
97 static_cast<const Element
*>(node
)->getAttribute(classAttr
) == interchangeNewlineClassString
;
100 static bool isInterchangeConvertedSpaceSpan(const Node
*node
)
102 DEFINE_STATIC_LOCAL(String
, convertedSpaceSpanClassString
, (AppleConvertedSpace
));
103 return node
->isHTMLElement() &&
104 static_cast<const HTMLElement
*>(node
)->getAttribute(classAttr
) == convertedSpaceSpanClassString
;
107 ReplacementFragment::ReplacementFragment(Document
* document
, DocumentFragment
* fragment
, bool matchStyle
, const VisibleSelection
& selection
)
108 : m_document(document
),
109 m_fragment(fragment
),
110 m_matchStyle(matchStyle
),
111 m_hasInterchangeNewlineAtStart(false),
112 m_hasInterchangeNewlineAtEnd(false)
118 if (!m_fragment
->firstChild())
121 Element
* editableRoot
= selection
.rootEditableElement();
122 ASSERT(editableRoot
);
126 Node
* shadowAncestorNode
= editableRoot
->shadowAncestorNode();
128 if (!editableRoot
->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent
) &&
129 // FIXME: Remove these checks once textareas and textfields actually register an event handler.
130 !(shadowAncestorNode
&& shadowAncestorNode
->renderer() && shadowAncestorNode
->renderer()->isTextControl()) &&
131 editableRoot
->isContentRichlyEditable()) {
132 removeInterchangeNodes(m_fragment
.get());
136 Node
* styleNode
= selection
.base().node();
137 RefPtr
<Node
> holder
= insertFragmentForTestRendering(styleNode
);
139 RefPtr
<Range
> range
= VisibleSelection::selectionFromContentsOfNode(holder
.get()).toNormalizedRange();
140 String text
= plainText(range
.get());
141 // Give the root a chance to change the text.
142 RefPtr
<BeforeTextInsertedEvent
> evt
= BeforeTextInsertedEvent::create(text
);
143 ExceptionCode ec
= 0;
144 editableRoot
->dispatchEvent(evt
, ec
);
146 if (text
!= evt
->text() || !editableRoot
->isContentRichlyEditable()) {
147 restoreTestRenderingNodesToFragment(holder
.get());
150 m_fragment
= createFragmentFromText(selection
.toNormalizedRange().get(), evt
->text());
151 if (!m_fragment
->firstChild())
153 holder
= insertFragmentForTestRendering(styleNode
);
156 removeInterchangeNodes(holder
.get());
158 removeUnrenderedNodes(holder
.get());
159 restoreTestRenderingNodesToFragment(holder
.get());
163 bool ReplacementFragment::isEmpty() const
165 return (!m_fragment
|| !m_fragment
->firstChild()) && !m_hasInterchangeNewlineAtStart
&& !m_hasInterchangeNewlineAtEnd
;
168 Node
*ReplacementFragment::firstChild() const
170 return m_fragment
? m_fragment
->firstChild() : 0;
173 Node
*ReplacementFragment::lastChild() const
175 return m_fragment
? m_fragment
->lastChild() : 0;
178 void ReplacementFragment::removeNodePreservingChildren(Node
*node
)
183 while (RefPtr
<Node
> n
= node
->firstChild()) {
185 insertNodeBefore(n
.release(), node
);
190 void ReplacementFragment::removeNode(PassRefPtr
<Node
> node
)
195 Node
*parent
= node
->parentNode();
199 ExceptionCode ec
= 0;
200 parent
->removeChild(node
.get(), ec
);
204 void ReplacementFragment::insertNodeBefore(PassRefPtr
<Node
> node
, Node
* refNode
)
206 if (!node
|| !refNode
)
209 Node
* parent
= refNode
->parentNode();
213 ExceptionCode ec
= 0;
214 parent
->insertBefore(node
, refNode
, ec
);
218 PassRefPtr
<Node
> ReplacementFragment::insertFragmentForTestRendering(Node
* context
)
220 Node
* body
= m_document
->body();
224 RefPtr
<StyledElement
> holder
= createDefaultParagraphElement(m_document
.get());
226 ExceptionCode ec
= 0;
228 // Copy the whitespace and user-select style from the context onto this element.
229 // FIXME: We should examine other style properties to see if they would be appropriate to consider during the test rendering.
231 while (n
&& !n
->isElementNode())
234 RefPtr
<CSSComputedStyleDeclaration
> conFontStyle
= computedStyle(n
);
235 CSSStyleDeclaration
* style
= holder
->style();
236 style
->setProperty(CSSPropertyWhiteSpace
, conFontStyle
->getPropertyValue(CSSPropertyWhiteSpace
), false, ec
);
238 style
->setProperty(CSSPropertyWebkitUserSelect
, conFontStyle
->getPropertyValue(CSSPropertyWebkitUserSelect
), false, ec
);
242 holder
->appendChild(m_fragment
, ec
);
245 body
->appendChild(holder
.get(), ec
);
248 m_document
->updateLayoutIgnorePendingStylesheets();
250 return holder
.release();
253 void ReplacementFragment::restoreTestRenderingNodesToFragment(Node
*holder
)
258 ExceptionCode ec
= 0;
259 while (RefPtr
<Node
> node
= holder
->firstChild()) {
260 holder
->removeChild(node
.get(), ec
);
262 m_fragment
->appendChild(node
.get(), ec
);
267 void ReplacementFragment::removeUnrenderedNodes(Node
* holder
)
269 Vector
<Node
*> unrendered
;
271 for (Node
* node
= holder
->firstChild(); node
; node
= node
->traverseNextNode(holder
))
272 if (!isNodeRendered(node
) && !isTableStructureNode(node
))
273 unrendered
.append(node
);
275 size_t n
= unrendered
.size();
276 for (size_t i
= 0; i
< n
; ++i
)
277 removeNode(unrendered
[i
]);
280 void ReplacementFragment::removeInterchangeNodes(Node
* container
)
282 // Interchange newlines at the "start" of the incoming fragment must be
283 // either the first node in the fragment or the first leaf in the fragment.
284 Node
* node
= container
->firstChild();
286 if (isInterchangeNewlineNode(node
)) {
287 m_hasInterchangeNewlineAtStart
= true;
291 node
= node
->firstChild();
293 if (!container
->hasChildNodes())
295 // Interchange newlines at the "end" of the incoming fragment must be
296 // either the last node in the fragment or the last leaf in the fragment.
297 node
= container
->lastChild();
299 if (isInterchangeNewlineNode(node
)) {
300 m_hasInterchangeNewlineAtEnd
= true;
304 node
= node
->lastChild();
307 node
= container
->firstChild();
309 Node
*next
= node
->traverseNextNode();
310 if (isInterchangeConvertedSpaceSpan(node
)) {
312 while ((n
= node
->firstChild())) {
314 insertNodeBefore(n
, node
);
318 next
= n
->traverseNextNode();
324 ReplaceSelectionCommand::ReplaceSelectionCommand(Document
* document
, PassRefPtr
<DocumentFragment
> fragment
,
325 bool selectReplacement
, bool smartReplace
, bool matchStyle
, bool preventNesting
, bool movingParagraph
,
326 EditAction editAction
)
327 : CompositeEditCommand(document
),
328 m_selectReplacement(selectReplacement
),
329 m_smartReplace(smartReplace
),
330 m_matchStyle(matchStyle
),
331 m_documentFragment(fragment
),
332 m_preventNesting(preventNesting
),
333 m_movingParagraph(movingParagraph
),
334 m_editAction(editAction
),
335 m_shouldMergeEnd(false)
339 static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent
, VisiblePosition endOfInsertedContent
)
341 Position existing
= endOfExistingContent
.deepEquivalent();
342 Position inserted
= endOfInsertedContent
.deepEquivalent();
343 bool isInsideMailBlockquote
= nearestMailBlockquote(inserted
.node());
344 return isInsideMailBlockquote
&& (numEnclosingMailBlockquotes(existing
) == numEnclosingMailBlockquotes(inserted
));
347 bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph
, bool fragmentHasInterchangeNewlineAtStart
, bool selectionStartWasInsideMailBlockquote
)
349 if (m_movingParagraph
)
352 VisiblePosition
startOfInsertedContent(positionAtStartOfInsertedContent());
353 VisiblePosition prev
= startOfInsertedContent
.previous(true);
357 // When we have matching quote levels, its ok to merge more frequently.
358 // For a successful merge, we still need to make sure that the inserted content starts with the beginning of a paragraph.
359 // And we should only merge here if the selection start was inside a mail blockquote. This prevents against removing a
360 // blockquote from newly pasted quoted content that was pasted into an unquoted position. If that unquoted position happens
361 // to be right after another blockquote, we don't want to merge and risk stripping a valid block (and newline) from the pasted content.
362 if (isStartOfParagraph(startOfInsertedContent
) && selectionStartWasInsideMailBlockquote
&& hasMatchingQuoteLevel(prev
, positionAtEndOfInsertedContent()))
365 return !selectionStartWasStartOfParagraph
&&
366 !fragmentHasInterchangeNewlineAtStart
&&
367 isStartOfParagraph(startOfInsertedContent
) &&
368 !startOfInsertedContent
.deepEquivalent().node()->hasTagName(brTag
) &&
369 shouldMerge(startOfInsertedContent
, prev
);
372 bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph
)
374 VisiblePosition
endOfInsertedContent(positionAtEndOfInsertedContent());
375 VisiblePosition next
= endOfInsertedContent
.next(true);
379 return !selectionEndWasEndOfParagraph
&&
380 isEndOfParagraph(endOfInsertedContent
) &&
381 !endOfInsertedContent
.deepEquivalent().node()->hasTagName(brTag
) &&
382 shouldMerge(endOfInsertedContent
, next
);
385 static bool isMailPasteAsQuotationNode(const Node
* node
)
387 return node
&& node
->hasTagName(blockquoteTag
) && node
->isElementNode() && static_cast<const Element
*>(node
)->getAttribute(classAttr
) == ApplePasteAsQuotation
;
390 // Wrap CompositeEditCommand::removeNodePreservingChildren() so we can update the nodes we track
391 void ReplaceSelectionCommand::removeNodePreservingChildren(Node
* node
)
393 if (m_firstNodeInserted
== node
)
394 m_firstNodeInserted
= node
->traverseNextNode();
395 if (m_lastLeafInserted
== node
)
396 m_lastLeafInserted
= node
->lastChild() ? node
->lastChild() : node
->traverseNextSibling();
397 CompositeEditCommand::removeNodePreservingChildren(node
);
400 // Wrap CompositeEditCommand::removeNodeAndPruneAncestors() so we can update the nodes we track
401 void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node
* node
)
403 // prepare in case m_firstNodeInserted and/or m_lastLeafInserted get removed
404 // FIXME: shouldn't m_lastLeafInserted be adjusted using traversePreviousNode()?
405 Node
* afterFirst
= m_firstNodeInserted
? m_firstNodeInserted
->traverseNextSibling() : 0;
406 Node
* afterLast
= m_lastLeafInserted
? m_lastLeafInserted
->traverseNextSibling() : 0;
408 CompositeEditCommand::removeNodeAndPruneAncestors(node
);
410 // adjust m_firstNodeInserted and m_lastLeafInserted since either or both may have been removed
411 if (m_lastLeafInserted
&& !m_lastLeafInserted
->inDocument())
412 m_lastLeafInserted
= afterLast
;
413 if (m_firstNodeInserted
&& !m_firstNodeInserted
->inDocument())
414 m_firstNodeInserted
= m_lastLeafInserted
&& m_lastLeafInserted
->inDocument() ? afterFirst
: 0;
417 static bool isHeaderElement(Node
* a
)
422 return a
->hasTagName(h1Tag
) ||
423 a
->hasTagName(h2Tag
) ||
424 a
->hasTagName(h3Tag
) ||
425 a
->hasTagName(h4Tag
) ||
426 a
->hasTagName(h5Tag
);
429 static bool haveSameTagName(Node
* a
, Node
* b
)
431 return a
&& b
&& a
->isElementNode() && b
->isElementNode() && static_cast<Element
*>(a
)->tagName() == static_cast<Element
*>(b
)->tagName();
434 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition
& source
, const VisiblePosition
& destination
)
436 if (source
.isNull() || destination
.isNull())
439 Node
* sourceNode
= source
.deepEquivalent().node();
440 Node
* destinationNode
= destination
.deepEquivalent().node();
441 Node
* sourceBlock
= enclosingBlock(sourceNode
);
442 Node
* destinationBlock
= enclosingBlock(destinationNode
);
443 return !enclosingNodeOfType(source
.deepEquivalent(), &isMailPasteAsQuotationNode
) &&
444 sourceBlock
&& (!sourceBlock
->hasTagName(blockquoteTag
) || isMailBlockquote(sourceBlock
)) &&
445 enclosingListChild(sourceBlock
) == enclosingListChild(destinationNode
) &&
446 enclosingTableCell(source
.deepEquivalent()) == enclosingTableCell(destination
.deepEquivalent()) &&
447 (!isHeaderElement(sourceBlock
) || haveSameTagName(sourceBlock
, destinationBlock
)) &&
448 // Don't merge to or from a position before or after a block because it would
449 // be a no-op and cause infinite recursion.
450 !isBlock(sourceNode
) && !isBlock(destinationNode
);
453 // Style rules that match just inserted elements could change their appearance, like
454 // a div inserted into a document with div { display:inline; }.
455 void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance()
457 for (RefPtr
<Node
> node
= m_firstNodeInserted
.get(); node
; node
= node
->traverseNextNode()) {
458 // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance
459 if (isStyleSpan(node
.get())) {
460 HTMLElement
* e
= static_cast<HTMLElement
*>(node
.get());
461 // There are other styles that style rules can give to style spans,
462 // but these are the two important ones because they'll prevent
463 // inserted content from appearing in the right paragraph.
464 // FIXME: Hyatt is concerned that selectively using display:inline will give inconsistent
465 // results. We already know one issue because td elements ignore their display property
466 // in quirks mode (which Mail.app is always in). We should look for an alternative.
468 e
->getInlineStyleDecl()->setProperty(CSSPropertyDisplay
, CSSValueInline
);
469 if (e
->renderer() && e
->renderer()->style()->floating() != FNONE
)
470 e
->getInlineStyleDecl()->setProperty(CSSPropertyFloat
, CSSValueNone
);
472 if (node
== m_lastLeafInserted
)
477 void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds()
479 document()->updateLayoutIgnorePendingStylesheets();
480 if (!m_lastLeafInserted
->renderer() &&
481 m_lastLeafInserted
->isTextNode() &&
482 !enclosingNodeWithTag(Position(m_lastLeafInserted
.get(), 0), selectTag
) &&
483 !enclosingNodeWithTag(Position(m_lastLeafInserted
.get(), 0), scriptTag
)) {
484 if (m_firstNodeInserted
== m_lastLeafInserted
) {
485 removeNode(m_lastLeafInserted
.get());
486 m_lastLeafInserted
= 0;
487 m_firstNodeInserted
= 0;
490 RefPtr
<Node
> previous
= m_lastLeafInserted
->traversePreviousNode();
491 removeNode(m_lastLeafInserted
.get());
492 m_lastLeafInserted
= previous
;
495 // We don't have to make sure that m_firstNodeInserted isn't inside a select or script element, because
496 // it is a top level node in the fragment and the user can't insert into those elements.
497 if (!m_firstNodeInserted
->renderer() &&
498 m_firstNodeInserted
->isTextNode()) {
499 if (m_firstNodeInserted
== m_lastLeafInserted
) {
500 removeNode(m_firstNodeInserted
.get());
501 m_firstNodeInserted
= 0;
502 m_lastLeafInserted
= 0;
505 RefPtr
<Node
> next
= m_firstNodeInserted
->traverseNextSibling();
506 removeNode(m_firstNodeInserted
.get());
507 m_firstNodeInserted
= next
;
511 void ReplaceSelectionCommand::handlePasteAsQuotationNode()
513 Node
* node
= m_firstNodeInserted
.get();
514 if (isMailPasteAsQuotationNode(node
))
515 removeNodeAttribute(static_cast<Element
*>(node
), classAttr
);
518 VisiblePosition
ReplaceSelectionCommand::positionAtEndOfInsertedContent()
520 Node
* lastNode
= m_lastLeafInserted
.get();
521 // FIXME: Why is this hack here? What's special about <select> tags?
522 Node
* enclosingSelect
= enclosingNodeWithTag(firstDeepEditingPositionForNode(lastNode
), selectTag
);
524 lastNode
= enclosingSelect
;
525 return lastDeepEditingPositionForNode(lastNode
);
528 VisiblePosition
ReplaceSelectionCommand::positionAtStartOfInsertedContent()
530 // Return the inserted content's first VisiblePosition.
531 return VisiblePosition(nextCandidate(positionInParentBeforeNode(m_firstNodeInserted
.get())));
534 // Remove style spans before insertion if they are unnecessary. It's faster because we'll
535 // avoid doing a layout.
536 static bool handleStyleSpansBeforeInsertion(ReplacementFragment
& fragment
, const Position
& insertionPos
)
538 Node
* topNode
= fragment
.firstChild();
540 // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans)
541 // and doesn't receive the optimization.
542 if (isMailPasteAsQuotationNode(topNode
) || nearestMailBlockquote(topNode
))
545 // Either there are no style spans in the fragment or a WebKit client has added content to the fragment
546 // before inserting it. Look for and handle style spans after insertion.
547 if (!isStyleSpan(topNode
))
550 Node
* sourceDocumentStyleSpan
= topNode
;
551 RefPtr
<Node
> copiedRangeStyleSpan
= sourceDocumentStyleSpan
->firstChild();
553 RefPtr
<CSSMutableStyleDeclaration
> styleAtInsertionPos
= editingStyleAtPosition(rangeCompliantEquivalent(insertionPos
));
555 String styleText
= styleAtInsertionPos
->cssText();
557 if (styleText
== static_cast<Element
*>(sourceDocumentStyleSpan
)->getAttribute(styleAttr
)) {
558 fragment
.removeNodePreservingChildren(sourceDocumentStyleSpan
);
559 if (!isStyleSpan(copiedRangeStyleSpan
.get()))
563 if (isStyleSpan(copiedRangeStyleSpan
.get()) && styleText
== static_cast<Element
*>(copiedRangeStyleSpan
.get())->getAttribute(styleAttr
)) {
564 fragment
.removeNodePreservingChildren(copiedRangeStyleSpan
.get());
571 // At copy time, WebKit wraps copied content in a span that contains the source document's
572 // default styles. If the copied Range inherits any other styles from its ancestors, we put
573 // those styles on a second span.
574 // This function removes redundant styles from those spans, and removes the spans if all their
575 // styles are redundant.
576 // We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>.
577 // We should remove styles from spans that are overridden by all of their children, either here
579 void ReplaceSelectionCommand::handleStyleSpans()
581 Node
* sourceDocumentStyleSpan
= 0;
582 Node
* copiedRangeStyleSpan
= 0;
583 // The style span that contains the source document's default style should be at
584 // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
585 // so search for the top level style span instead of assuming it's at the top.
586 for (Node
* node
= m_firstNodeInserted
.get(); node
; node
= node
->traverseNextNode()) {
587 if (isStyleSpan(node
)) {
588 sourceDocumentStyleSpan
= node
;
589 // If the copied Range's common ancestor had user applied inheritable styles
590 // on it, they'll be on a second style span, just below the one that holds the
591 // document defaults.
592 if (isStyleSpan(node
->firstChild()))
593 copiedRangeStyleSpan
= node
->firstChild();
598 // There might not be any style spans if we're pasting from another application or if
599 // we are here because of a document.execCommand("InsertHTML", ...) call.
600 if (!sourceDocumentStyleSpan
)
603 RefPtr
<CSSMutableStyleDeclaration
> sourceDocumentStyle
= static_cast<HTMLElement
*>(sourceDocumentStyleSpan
)->getInlineStyleDecl()->copy();
604 Node
* context
= sourceDocumentStyleSpan
->parentNode();
606 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region,
607 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
608 Node
* blockquoteNode
= isMailPasteAsQuotationNode(context
) ? context
: nearestMailBlockquote(context
);
609 if (blockquoteNode
) {
610 RefPtr
<CSSMutableStyleDeclaration
> blockquoteStyle
= editingStyleAtPosition(Position(blockquoteNode
, 0));
611 RefPtr
<CSSMutableStyleDeclaration
> parentStyle
= editingStyleAtPosition(Position(blockquoteNode
->parentNode(), 0));
612 parentStyle
->diff(blockquoteStyle
.get());
614 CSSMutableStyleDeclaration::const_iterator end
= blockquoteStyle
->end();
615 for (CSSMutableStyleDeclaration::const_iterator it
= blockquoteStyle
->begin(); it
!= end
; ++it
) {
616 const CSSProperty
& property
= *it
;
617 sourceDocumentStyle
->removeProperty(property
.id());
620 context
= blockquoteNode
->parentNode();
623 // This operation requires that only editing styles to be removed from sourceDocumentStyle.
624 prepareEditingStyleToApplyAt(sourceDocumentStyle
.get(), Position(context
, 0));
626 // Remove block properties in the span's style. This prevents properties that probably have no effect
627 // currently from affecting blocks later if the style is cloned for a new block element during a future
628 // editing operation.
629 // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked
630 // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might.
631 sourceDocumentStyle
->removeBlockProperties();
633 // The styles on sourceDocumentStyleSpan are all redundant, and there is no copiedRangeStyleSpan
634 // to consider. We're finished.
635 if (sourceDocumentStyle
->length() == 0 && !copiedRangeStyleSpan
) {
636 removeNodePreservingChildren(sourceDocumentStyleSpan
);
640 // There are non-redundant styles on sourceDocumentStyleSpan, but there is no
641 // copiedRangeStyleSpan. Clear the redundant styles from sourceDocumentStyleSpan
643 if (sourceDocumentStyle
->length() > 0 && !copiedRangeStyleSpan
) {
644 setNodeAttribute(static_cast<Element
*>(sourceDocumentStyleSpan
), styleAttr
, sourceDocumentStyle
->cssText());
648 RefPtr
<CSSMutableStyleDeclaration
> copiedRangeStyle
= static_cast<HTMLElement
*>(copiedRangeStyleSpan
)->getInlineStyleDecl()->copy();
650 // We're going to put sourceDocumentStyleSpan's non-redundant styles onto copiedRangeStyleSpan,
651 // as long as they aren't overridden by ones on copiedRangeStyleSpan.
652 sourceDocumentStyle
->merge(copiedRangeStyle
.get(), true);
653 copiedRangeStyle
= sourceDocumentStyle
;
655 removeNodePreservingChildren(sourceDocumentStyleSpan
);
657 // Remove redundant styles.
658 context
= copiedRangeStyleSpan
->parentNode();
659 prepareEditingStyleToApplyAt(copiedRangeStyle
.get(), Position(context
, 0));
661 // See the comments above about removing block properties.
662 copiedRangeStyle
->removeBlockProperties();
664 // All the styles on copiedRangeStyleSpan are redundant, remove it.
665 if (copiedRangeStyle
->length() == 0) {
666 removeNodePreservingChildren(copiedRangeStyleSpan
);
670 // Clear the redundant styles from the span's style attribute.
671 // FIXME: If font-family:-webkit-monospace is non-redundant, then the font-size should stay, even if it
672 // appears redundant.
673 setNodeAttribute(static_cast<Element
*>(copiedRangeStyleSpan
), styleAttr
, copiedRangeStyle
->cssText());
676 void ReplaceSelectionCommand::mergeEndIfNeeded()
678 if (!m_shouldMergeEnd
)
681 VisiblePosition
startOfInsertedContent(positionAtStartOfInsertedContent());
682 VisiblePosition
endOfInsertedContent(positionAtEndOfInsertedContent());
684 // Bail to avoid infinite recursion.
685 if (m_movingParagraph
) {
686 ASSERT_NOT_REACHED();
690 // Merging two paragraphs will destroy the moved one's block styles. Always move the end of inserted forward
691 // to preserve the block style of the paragraph already in the document, unless the paragraph to move would
692 // include the what was the start of the selection that was pasted into, so that we preserve that paragraph's
694 bool mergeForward
= !(inSameParagraph(startOfInsertedContent
, endOfInsertedContent
) && !isStartOfParagraph(startOfInsertedContent
));
696 VisiblePosition destination
= mergeForward
? endOfInsertedContent
.next() : endOfInsertedContent
;
697 VisiblePosition startOfParagraphToMove
= mergeForward
? startOfParagraph(endOfInsertedContent
) : endOfInsertedContent
.next();
699 // Merging forward could result in deleting the destination anchor node.
700 // To avoid this, we add a placeholder node before the start of the paragraph.
701 if (endOfParagraph(startOfParagraphToMove
) == destination
) {
702 RefPtr
<Node
> placeholder
= createBreakElement(document());
703 insertNodeBefore(placeholder
, startOfParagraphToMove
.deepEquivalent().node());
704 destination
= VisiblePosition(Position(placeholder
.get(), 0));
707 moveParagraph(startOfParagraphToMove
, endOfParagraph(startOfParagraphToMove
), destination
);
709 // Merging forward will remove m_lastLeafInserted from the document.
710 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
711 // only ever used to create positions where inserted content starts/ends. Also, we sometimes insert content
712 // directly into text nodes already in the document, in which case tracking inserted nodes is inadequate.
714 m_lastLeafInserted
= destination
.previous().deepEquivalent().node();
715 if (!m_firstNodeInserted
->inDocument())
716 m_firstNodeInserted
= endingSelection().visibleStart().deepEquivalent().node();
717 // If we merged text nodes, m_lastLeafInserted could be null. If this is the case,
718 // we use m_firstNodeInserted.
719 if (!m_lastLeafInserted
)
720 m_lastLeafInserted
= m_firstNodeInserted
;
724 void ReplaceSelectionCommand::doApply()
726 VisibleSelection selection
= endingSelection();
727 ASSERT(selection
.isCaretOrRange());
728 ASSERT(selection
.start().node());
729 if (selection
.isNone() || !selection
.start().node())
732 bool selectionIsPlainText
= !selection
.isContentRichlyEditable();
734 Element
* currentRoot
= selection
.rootEditableElement();
735 ReplacementFragment
fragment(document(), m_documentFragment
.get(), m_matchStyle
, selection
);
737 if (performTrivialReplace(fragment
))
741 m_insertionStyle
= editingStyleAtPosition(selection
.start(), IncludeTypingStyle
);
743 VisiblePosition visibleStart
= selection
.visibleStart();
744 VisiblePosition visibleEnd
= selection
.visibleEnd();
746 bool selectionEndWasEndOfParagraph
= isEndOfParagraph(visibleEnd
);
747 bool selectionStartWasStartOfParagraph
= isStartOfParagraph(visibleStart
);
749 Node
* startBlock
= enclosingBlock(visibleStart
.deepEquivalent().node());
751 Position insertionPos
= selection
.start();
752 bool startIsInsideMailBlockquote
= nearestMailBlockquote(insertionPos
.node());
754 if ((selectionStartWasStartOfParagraph
&& selectionEndWasEndOfParagraph
&& !startIsInsideMailBlockquote
) ||
755 startBlock
== currentRoot
||
756 (startBlock
&& startBlock
->renderer() && startBlock
->renderer()->isListItem()) ||
757 selectionIsPlainText
)
758 m_preventNesting
= false;
760 if (selection
.isRange()) {
761 // When the end of the selection being pasted into is at the end of a paragraph, and that selection
762 // spans multiple blocks, not merging may leave an empty line.
763 // When the start of the selection being pasted into is at the start of a block, not merging
764 // will leave hanging block(s).
765 // Merge blocks if the start of the selection was in a Mail blockquote, since we handle
766 // that case specially to prevent nesting.
767 bool mergeBlocksAfterDelete
= startIsInsideMailBlockquote
|| isEndOfParagraph(visibleEnd
) || isStartOfBlock(visibleStart
);
768 // FIXME: We should only expand to include fully selected special elements if we are copying a
769 // selection and pasting it on top of itself.
770 deleteSelection(false, mergeBlocksAfterDelete
, true, false);
771 visibleStart
= endingSelection().visibleStart();
772 if (fragment
.hasInterchangeNewlineAtStart()) {
773 if (isEndOfParagraph(visibleStart
) && !isStartOfParagraph(visibleStart
)) {
774 if (!isEndOfDocument(visibleStart
))
775 setEndingSelection(visibleStart
.next());
777 insertParagraphSeparator();
779 insertionPos
= endingSelection().start();
781 ASSERT(selection
.isCaret());
782 if (fragment
.hasInterchangeNewlineAtStart()) {
783 VisiblePosition next
= visibleStart
.next(true);
784 if (isEndOfParagraph(visibleStart
) && !isStartOfParagraph(visibleStart
) && next
.isNotNull())
785 setEndingSelection(next
);
787 insertParagraphSeparator();
789 // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block.
790 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret.
791 // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>,
792 // not <div>xbar<div>bar</div><div>bazx</div></div>.
793 // Don't do this if the selection started in a Mail blockquote.
794 if (m_preventNesting
&& !startIsInsideMailBlockquote
&& !isEndOfParagraph(visibleStart
) && !isStartOfParagraph(visibleStart
)) {
795 insertParagraphSeparator();
796 setEndingSelection(endingSelection().visibleStart().previous());
798 insertionPos
= endingSelection().start();
801 // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break
802 // out of any surrounding Mail blockquotes. Unless we're inserting in a table, in which case
803 // breaking the blockquote will prevent the content from actually being inserted in the table.
804 if (startIsInsideMailBlockquote
&& m_preventNesting
&& !(enclosingNodeOfType(insertionPos
, &isTableStructureNode
))) {
805 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
806 // This will leave a br between the split.
807 Node
* br
= endingSelection().start().node();
808 ASSERT(br
->hasTagName(brTag
));
809 // Insert content between the two blockquotes, but remove the br (since it was just a placeholder).
810 insertionPos
= positionInParentBeforeNode(br
);
814 // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world.
815 prepareWhitespaceAtPositionForSplit(insertionPos
);
817 // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after
818 // 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
819 // away, there are positions after the br which map to the same visible position as [br, 0]).
820 Node
* endBR
= insertionPos
.downstream().node()->hasTagName(brTag
) ? insertionPos
.downstream().node() : 0;
821 VisiblePosition originalVisPosBeforeEndBR
;
823 originalVisPosBeforeEndBR
= VisiblePosition(endBR
, 0, DOWNSTREAM
).previous();
825 startBlock
= enclosingBlock(insertionPos
.node());
827 // Adjust insertionPos to prevent nesting.
828 // If the start was in a Mail blockquote, we will have already handled adjusting insertionPos above.
829 if (m_preventNesting
&& startBlock
&& !startIsInsideMailBlockquote
) {
830 ASSERT(startBlock
!= currentRoot
);
831 VisiblePosition
visibleInsertionPos(insertionPos
);
832 if (isEndOfBlock(visibleInsertionPos
) && !(isStartOfBlock(visibleInsertionPos
) && fragment
.hasInterchangeNewlineAtEnd()))
833 insertionPos
= positionInParentAfterNode(startBlock
);
834 else if (isStartOfBlock(visibleInsertionPos
))
835 insertionPos
= positionInParentBeforeNode(startBlock
);
838 // Paste into run of tabs splits the tab span.
839 insertionPos
= positionOutsideTabSpan(insertionPos
);
841 // Paste at start or end of link goes outside of link.
842 insertionPos
= positionAvoidingSpecialElementBoundary(insertionPos
);
844 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
845 // any work performed after this that queries or uses the typing style.
846 if (Frame
* frame
= document()->frame())
847 frame
->clearTypingStyle();
849 bool handledStyleSpans
= handleStyleSpansBeforeInsertion(fragment
, insertionPos
);
851 // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try
852 // again here if they've been removed.
854 // We're finished if there is nothing to add.
855 if (fragment
.isEmpty() || !fragment
.firstChild())
858 // 1) Insert the content.
859 // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>.
860 // 3) Merge the start of the added content with the content before the position being pasted into.
861 // 4) Do one of the following: a) expand the last br if the fragment ends with one and it collapsed,
862 // b) merge the last paragraph of the incoming fragment with the paragraph that contained the
863 // end of the selection that was pasted into, or c) handle an interchange newline at the end of the
864 // incoming fragment.
865 // 5) Add spaces for smart replace.
866 // 6) Select the replacement if requested, and match style if requested.
868 VisiblePosition startOfInsertedContent
, endOfInsertedContent
;
870 RefPtr
<Node
> refNode
= fragment
.firstChild();
871 RefPtr
<Node
> node
= refNode
->nextSibling();
873 fragment
.removeNode(refNode
);
874 insertNodeAtAndUpdateNodesInserted(refNode
, insertionPos
);
876 // Mutation events (bug 22634) may have already removed the inserted content
877 if (!refNode
->inDocument())
881 Node
* next
= node
->nextSibling();
882 fragment
.removeNode(node
);
883 insertNodeAfterAndUpdateNodesInserted(node
, refNode
.get());
885 // Mutation events (bug 22634) may have already removed the inserted content
886 if (!node
->inDocument())
893 removeUnrenderedTextNodesAtEnds();
895 negateStyleRulesThatAffectAppearance();
897 if (!handledStyleSpans
)
900 // Mutation events (bug 20161) may have already removed the inserted content
901 if (!m_firstNodeInserted
|| !m_firstNodeInserted
->inDocument())
904 endOfInsertedContent
= positionAtEndOfInsertedContent();
905 startOfInsertedContent
= positionAtStartOfInsertedContent();
907 // We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
908 // didn't have a br after it, so the inserted content ended up in the same paragraph.
909 if (startBlock
&& insertionPos
.node() == startBlock
->parentNode() && (unsigned)insertionPos
.deprecatedEditingOffset() < startBlock
->nodeIndex() && !isStartOfParagraph(startOfInsertedContent
))
910 insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent
.deepEquivalent());
912 Position lastPositionToSelect
;
914 bool interchangeNewlineAtEnd
= fragment
.hasInterchangeNewlineAtEnd();
916 if (shouldRemoveEndBR(endBR
, originalVisPosBeforeEndBR
))
917 removeNodeAndPruneAncestors(endBR
);
919 // Determine whether or not we should merge the end of inserted content with what's after it before we do
920 // the start merge so that the start merge doesn't effect our decision.
921 m_shouldMergeEnd
= shouldMergeEnd(selectionEndWasEndOfParagraph
);
923 if (shouldMergeStart(selectionStartWasStartOfParagraph
, fragment
.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote
)) {
924 VisiblePosition destination
= startOfInsertedContent
.previous();
925 VisiblePosition startOfParagraphToMove
= startOfInsertedContent
;
927 // Merging the the first paragraph of inserted content with the content that came
928 // before the selection that was pasted into would also move content after
929 // the selection that was pasted into if: only one paragraph was being pasted,
930 // and it was not wrapped in a block, the selection that was pasted into ended
931 // at the end of a block and the next paragraph didn't start at the start of a block.
932 // Insert a line break just after the inserted content to separate it from what
933 // comes after and prevent that from happening.
934 VisiblePosition endOfInsertedContent
= positionAtEndOfInsertedContent();
935 if (startOfParagraph(endOfInsertedContent
) == startOfParagraphToMove
) {
936 insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent
.deepEquivalent());
937 // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move
938 if (!startOfParagraphToMove
.deepEquivalent().node()->inDocument())
942 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
943 // only ever used to create positions where inserted content starts/ends.
944 moveParagraph(startOfParagraphToMove
, endOfParagraph(startOfParagraphToMove
), destination
);
945 m_firstNodeInserted
= endingSelection().visibleStart().deepEquivalent().downstream().node();
946 if (!m_lastLeafInserted
->inDocument())
947 m_lastLeafInserted
= endingSelection().visibleEnd().deepEquivalent().upstream().node();
950 endOfInsertedContent
= positionAtEndOfInsertedContent();
951 startOfInsertedContent
= positionAtStartOfInsertedContent();
953 if (interchangeNewlineAtEnd
) {
954 VisiblePosition next
= endOfInsertedContent
.next(true);
956 if (selectionEndWasEndOfParagraph
|| !isEndOfParagraph(endOfInsertedContent
) || next
.isNull()) {
957 if (!isStartOfParagraph(endOfInsertedContent
)) {
958 setEndingSelection(endOfInsertedContent
);
959 // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph
960 // block's style seems to annoy users.
961 insertParagraphSeparator(true);
963 // Select up to the paragraph separator that was added.
964 lastPositionToSelect
= endingSelection().visibleStart().deepEquivalent();
965 updateNodesInserted(lastPositionToSelect
.node());
968 // Select up to the beginning of the next paragraph.
969 lastPositionToSelect
= next
.deepEquivalent().downstream();
975 handlePasteAsQuotationNode();
977 endOfInsertedContent
= positionAtEndOfInsertedContent();
978 startOfInsertedContent
= positionAtStartOfInsertedContent();
980 // Add spaces for smart replace.
981 if (m_smartReplace
&& currentRoot
) {
982 // Disable smart replace for password fields.
983 Node
* start
= currentRoot
->shadowAncestorNode();
984 if (start
->hasTagName(inputTag
) && static_cast<HTMLInputElement
*>(start
)->inputType() == HTMLInputElement::PASSWORD
)
985 m_smartReplace
= false;
987 if (m_smartReplace
) {
988 bool needsTrailingSpace
= !isEndOfParagraph(endOfInsertedContent
) &&
989 !isCharacterSmartReplaceExempt(endOfInsertedContent
.characterAfter(), false);
990 if (needsTrailingSpace
) {
991 RenderObject
* renderer
= m_lastLeafInserted
->renderer();
992 bool collapseWhiteSpace
= !renderer
|| renderer
->style()->collapseWhiteSpace();
993 Node
* endNode
= positionAtEndOfInsertedContent().deepEquivalent().upstream().node();
994 if (endNode
->isTextNode()) {
995 Text
* text
= static_cast<Text
*>(endNode
);
996 insertTextIntoNode(text
, text
->length(), collapseWhiteSpace
? nonBreakingSpaceString() : " ");
998 RefPtr
<Node
> node
= document()->createEditingTextNode(collapseWhiteSpace
? nonBreakingSpaceString() : " ");
999 insertNodeAfterAndUpdateNodesInserted(node
, endNode
);
1003 bool needsLeadingSpace
= !isStartOfParagraph(startOfInsertedContent
) &&
1004 !isCharacterSmartReplaceExempt(startOfInsertedContent
.previous().characterAfter(), true);
1005 if (needsLeadingSpace
) {
1006 RenderObject
* renderer
= m_lastLeafInserted
->renderer();
1007 bool collapseWhiteSpace
= !renderer
|| renderer
->style()->collapseWhiteSpace();
1008 Node
* startNode
= positionAtStartOfInsertedContent().deepEquivalent().downstream().node();
1009 if (startNode
->isTextNode()) {
1010 Text
* text
= static_cast<Text
*>(startNode
);
1011 insertTextIntoNode(text
, 0, collapseWhiteSpace
? nonBreakingSpaceString() : " ");
1013 RefPtr
<Node
> node
= document()->createEditingTextNode(collapseWhiteSpace
? nonBreakingSpaceString() : " ");
1014 // Don't updateNodesInserted. Doing so would set m_lastLeafInserted to be the node containing the
1015 // leading space, but m_lastLeafInserted is supposed to mark the end of pasted content.
1016 insertNodeBefore(node
, startNode
);
1017 // FIXME: Use positions to track the start/end of inserted content.
1018 m_firstNodeInserted
= node
;
1023 completeHTMLReplacement(lastPositionToSelect
);
1026 bool ReplaceSelectionCommand::shouldRemoveEndBR(Node
* endBR
, const VisiblePosition
& originalVisPosBeforeEndBR
)
1028 if (!endBR
|| !endBR
->inDocument())
1031 VisiblePosition
visiblePos(Position(endBR
, 0));
1033 // Don't remove the br if nothing was inserted.
1034 if (visiblePos
.previous() == originalVisPosBeforeEndBR
)
1037 // Remove the br if it is collapsed away and so is unnecessary.
1038 if (!document()->inStrictMode() && isEndOfBlock(visiblePos
) && !isStartOfParagraph(visiblePos
))
1041 // A br that was originally holding a line open should be displaced by inserted content or turned into a line break.
1042 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
1043 return isStartOfParagraph(visiblePos
) && isEndOfParagraph(visiblePos
);
1046 void ReplaceSelectionCommand::completeHTMLReplacement(const Position
&lastPositionToSelect
)
1051 // FIXME: This should never not be the case.
1052 if (m_firstNodeInserted
&& m_firstNodeInserted
->inDocument() && m_lastLeafInserted
&& m_lastLeafInserted
->inDocument()) {
1054 start
= positionAtStartOfInsertedContent().deepEquivalent();
1055 end
= positionAtEndOfInsertedContent().deepEquivalent();
1057 // FIXME (11475): Remove this and require that the creator of the fragment to use nbsps.
1058 rebalanceWhitespaceAt(start
);
1059 rebalanceWhitespaceAt(end
);
1062 ASSERT(m_insertionStyle
);
1063 applyStyle(m_insertionStyle
.get(), start
, end
);
1066 if (lastPositionToSelect
.isNotNull())
1067 end
= lastPositionToSelect
;
1068 } else if (lastPositionToSelect
.isNotNull())
1069 start
= end
= lastPositionToSelect
;
1073 if (m_selectReplacement
)
1074 setEndingSelection(VisibleSelection(start
, end
, SEL_DEFAULT_AFFINITY
));
1076 setEndingSelection(VisibleSelection(end
, SEL_DEFAULT_AFFINITY
));
1079 EditAction
ReplaceSelectionCommand::editingAction() const
1081 return m_editAction
;
1084 void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(PassRefPtr
<Node
> insertChild
, Node
* refChild
)
1086 Node
* nodeToUpdate
= insertChild
.get(); // insertChild will be cleared when passed
1087 insertNodeAfter(insertChild
, refChild
);
1088 updateNodesInserted(nodeToUpdate
);
1091 void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(PassRefPtr
<Node
> insertChild
, const Position
& p
)
1093 Node
* nodeToUpdate
= insertChild
.get(); // insertChild will be cleared when passed
1094 insertNodeAt(insertChild
, p
);
1095 updateNodesInserted(nodeToUpdate
);
1098 void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(PassRefPtr
<Node
> insertChild
, Node
* refChild
)
1100 Node
* nodeToUpdate
= insertChild
.get(); // insertChild will be cleared when passed
1101 insertNodeBefore(insertChild
, refChild
);
1102 updateNodesInserted(nodeToUpdate
);
1105 void ReplaceSelectionCommand::updateNodesInserted(Node
*node
)
1110 if (!m_firstNodeInserted
)
1111 m_firstNodeInserted
= node
;
1113 if (node
== m_lastLeafInserted
)
1116 m_lastLeafInserted
= node
->lastDescendant();
1119 // During simple pastes, where we're just pasting a text node into a run of text, we insert the text node
1120 // directly into the text node that holds the selection. This is much faster than the generalized code in
1121 // ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.cgi?id=6148> since we don't
1122 // split text nodes.
1123 bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment
& fragment
)
1125 if (!fragment
.firstChild() || fragment
.firstChild() != fragment
.lastChild() || !fragment
.firstChild()->isTextNode())
1128 // FIXME: Would be nice to handle smart replace in the fast path.
1129 if (m_smartReplace
|| fragment
.hasInterchangeNewlineAtStart() || fragment
.hasInterchangeNewlineAtEnd())
1132 Text
* textNode
= static_cast<Text
*>(fragment
.firstChild());
1133 // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here.
1134 String
text(textNode
->data());
1136 Position start
= endingSelection().start();
1137 Position end
= endingSelection().end();
1139 if (start
.anchorNode() != end
.anchorNode() || !start
.anchorNode()->isTextNode())
1142 replaceTextInNode(static_cast<Text
*>(start
.anchorNode()), start
.offsetInContainerNode(), end
.offsetInContainerNode() - start
.offsetInContainerNode(), text
);
1144 end
= Position(start
.anchorNode(), start
.offsetInContainerNode() + text
.length());
1146 VisibleSelection
selectionAfterReplace(m_selectReplacement
? start
: end
, end
);
1148 setEndingSelection(selectionAfterReplace
);
1153 } // namespace WebCore