1/* 2 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "ReplaceSelectionCommand.h" 29 30#include "ApplyStyleCommand.h" 31#include "BeforeTextInsertedEvent.h" 32#include "BreakBlockquoteCommand.h" 33#include "CSSStyleDeclaration.h" 34#include "Document.h" 35#include "DocumentFragment.h" 36#include "Element.h" 37#include "ElementIterator.h" 38#include "EventNames.h" 39#include "ExceptionCodePlaceholder.h" 40#include "Frame.h" 41#include "FrameSelection.h" 42#include "HTMLInputElement.h" 43#include "HTMLNames.h" 44#include "HTMLTitleElement.h" 45#include "NodeList.h" 46#include "NodeRenderStyle.h" 47#include "RenderInline.h" 48#include "RenderObject.h" 49#include "RenderText.h" 50#include "SimplifyMarkupCommand.h" 51#include "SmartReplace.h" 52#include "StyleProperties.h" 53#include "Text.h" 54#include "TextIterator.h" 55#include "VisibleUnits.h" 56#include "htmlediting.h" 57#include "markup.h" 58#include <wtf/NeverDestroyed.h> 59#include <wtf/StdLibExtras.h> 60 61namespace WebCore { 62 63using namespace HTMLNames; 64 65enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment }; 66 67// --- ReplacementFragment helper class 68 69class ReplacementFragment { 70 WTF_MAKE_NONCOPYABLE(ReplacementFragment); 71public: 72 ReplacementFragment(Document&, DocumentFragment*, const VisibleSelection&); 73 74 DocumentFragment* fragment() { return m_fragment.get(); } 75 76 Node* firstChild() const; 77 Node* lastChild() const; 78 79 bool isEmpty() const; 80 81 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; } 82 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; } 83 84 void removeNode(PassRefPtr<Node>); 85 void removeNodePreservingChildren(PassRefPtr<Node>); 86 87private: 88 PassRefPtr<StyledElement> insertFragmentForTestRendering(Node* rootEditableNode); 89 void removeUnrenderedNodes(Node*); 90 void restoreAndRemoveTestRenderingNodesToFragment(StyledElement*); 91 void removeInterchangeNodes(Node*); 92 93 void insertNodeBefore(PassRefPtr<Node> node, Node* refNode); 94 95 Document& document() { return *m_document; } 96 97 RefPtr<Document> m_document; 98 RefPtr<DocumentFragment> m_fragment; 99 bool m_hasInterchangeNewlineAtStart; 100 bool m_hasInterchangeNewlineAtEnd; 101}; 102 103static bool isInterchangeNewlineNode(const Node *node) 104{ 105 DEPRECATED_DEFINE_STATIC_LOCAL(String, interchangeNewlineClassString, (AppleInterchangeNewline)); 106 return node && node->hasTagName(brTag) && 107 static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString; 108} 109 110static bool isInterchangeConvertedSpaceSpan(const Node *node) 111{ 112 DEPRECATED_DEFINE_STATIC_LOCAL(String, convertedSpaceSpanClassString, (AppleConvertedSpace)); 113 return node->isHTMLElement() && 114 static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString; 115} 116 117static Position positionAvoidingPrecedingNodes(Position pos) 118{ 119 // If we're already on a break, it's probably a placeholder and we shouldn't change our position. 120 if (editingIgnoresContent(pos.deprecatedNode())) 121 return pos; 122 123 // We also stop when changing block flow elements because even though the visual position is the 124 // same. E.g., 125 // <div>foo^</div>^ 126 // The two positions above are the same visual position, but we want to stay in the same block. 127 Node* enclosingBlockNode = enclosingBlock(pos.containerNode()); 128 for (Position nextPosition = pos; nextPosition.containerNode() != enclosingBlockNode; pos = nextPosition) { 129 if (lineBreakExistsAtPosition(pos)) 130 break; 131 132 if (pos.containerNode()->nonShadowBoundaryParentNode()) 133 nextPosition = positionInParentAfterNode(pos.containerNode()); 134 135 if (nextPosition == pos 136 || enclosingBlock(nextPosition.containerNode()) != enclosingBlockNode 137 || VisiblePosition(pos) != VisiblePosition(nextPosition)) 138 break; 139 } 140 return pos; 141} 142 143ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* fragment, const VisibleSelection& selection) 144 : m_document(&document) 145 , m_fragment(fragment) 146 , m_hasInterchangeNewlineAtStart(false) 147 , m_hasInterchangeNewlineAtEnd(false) 148{ 149 if (!m_fragment) 150 return; 151 if (!m_fragment->firstChild()) 152 return; 153 154 RefPtr<Element> editableRoot = selection.rootEditableElement(); 155 ASSERT(editableRoot); 156 if (!editableRoot) 157 return; 158 159 Node* shadowAncestorNode = editableRoot->deprecatedShadowAncestorNode(); 160 161 if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) && 162 // FIXME: Remove these checks once textareas and textfields actually register an event handler. 163 !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) && 164 editableRoot->hasRichlyEditableStyle()) { 165 removeInterchangeNodes(m_fragment.get()); 166 return; 167 } 168 169 RefPtr<StyledElement> holder = insertFragmentForTestRendering(editableRoot.get()); 170 if (!holder) { 171 removeInterchangeNodes(m_fragment.get()); 172 return; 173 } 174 175 RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange(); 176 String text = plainText(range.get(), static_cast<TextIteratorBehavior>(TextIteratorEmitsOriginalText | TextIteratorIgnoresStyleVisibility)); 177 178 removeInterchangeNodes(holder.get()); 179 removeUnrenderedNodes(holder.get()); 180 restoreAndRemoveTestRenderingNodesToFragment(holder.get()); 181 182 // Give the root a chance to change the text. 183 RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); 184 editableRoot->dispatchEvent(evt, ASSERT_NO_EXCEPTION); 185 if (text != evt->text() || !editableRoot->hasRichlyEditableStyle()) { 186 restoreAndRemoveTestRenderingNodesToFragment(holder.get()); 187 188 RefPtr<Range> range = selection.toNormalizedRange(); 189 if (!range) 190 return; 191 192 m_fragment = createFragmentFromText(*range, evt->text()); 193 if (!m_fragment->firstChild()) 194 return; 195 196 holder = insertFragmentForTestRendering(editableRoot.get()); 197 removeInterchangeNodes(holder.get()); 198 removeUnrenderedNodes(holder.get()); 199 restoreAndRemoveTestRenderingNodesToFragment(holder.get()); 200 } 201} 202 203bool ReplacementFragment::isEmpty() const 204{ 205 return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd; 206} 207 208Node *ReplacementFragment::firstChild() const 209{ 210 return m_fragment ? m_fragment->firstChild() : 0; 211} 212 213Node *ReplacementFragment::lastChild() const 214{ 215 return m_fragment ? m_fragment->lastChild() : 0; 216} 217 218void ReplacementFragment::removeNodePreservingChildren(PassRefPtr<Node> node) 219{ 220 if (!node) 221 return; 222 223 while (RefPtr<Node> n = node->firstChild()) { 224 removeNode(n); 225 insertNodeBefore(n.release(), node.get()); 226 } 227 removeNode(node); 228} 229 230void ReplacementFragment::removeNode(PassRefPtr<Node> node) 231{ 232 if (!node) 233 return; 234 235 ContainerNode* parent = node->nonShadowBoundaryParentNode(); 236 if (!parent) 237 return; 238 239 parent->removeChild(node.get(), ASSERT_NO_EXCEPTION); 240} 241 242void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode) 243{ 244 if (!node || !refNode) 245 return; 246 247 ContainerNode* parent = refNode->nonShadowBoundaryParentNode(); 248 if (!parent) 249 return; 250 251 parent->insertBefore(node, refNode, ASSERT_NO_EXCEPTION); 252} 253 254PassRefPtr<StyledElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootEditableElement) 255{ 256 RefPtr<StyledElement> holder = createDefaultParagraphElement(document()); 257 258 holder->appendChild(m_fragment, ASSERT_NO_EXCEPTION); 259 rootEditableElement->appendChild(holder.get(), ASSERT_NO_EXCEPTION); 260 document().updateLayoutIgnorePendingStylesheets(); 261 262 return holder.release(); 263} 264 265void ReplacementFragment::restoreAndRemoveTestRenderingNodesToFragment(StyledElement* holder) 266{ 267 if (!holder) 268 return; 269 270 while (RefPtr<Node> node = holder->firstChild()) { 271 holder->removeChild(node.get(), ASSERT_NO_EXCEPTION); 272 m_fragment->appendChild(node.get(), ASSERT_NO_EXCEPTION); 273 } 274 275 removeNode(holder); 276} 277 278void ReplacementFragment::removeUnrenderedNodes(Node* holder) 279{ 280 Vector<RefPtr<Node>> unrendered; 281 282 for (Node* node = holder->firstChild(); node; node = NodeTraversal::next(node, holder)) 283 if (!isNodeRendered(node) && !isTableStructureNode(node)) 284 unrendered.append(node); 285 286 size_t n = unrendered.size(); 287 for (size_t i = 0; i < n; ++i) 288 removeNode(unrendered[i]); 289} 290 291void ReplacementFragment::removeInterchangeNodes(Node* container) 292{ 293 m_hasInterchangeNewlineAtStart = false; 294 m_hasInterchangeNewlineAtEnd = false; 295 296 // Interchange newlines at the "start" of the incoming fragment must be 297 // either the first node in the fragment or the first leaf in the fragment. 298 Node* node = container->firstChild(); 299 while (node) { 300 if (isInterchangeNewlineNode(node)) { 301 m_hasInterchangeNewlineAtStart = true; 302 removeNode(node); 303 break; 304 } 305 node = node->firstChild(); 306 } 307 if (!container->hasChildNodes()) 308 return; 309 // Interchange newlines at the "end" of the incoming fragment must be 310 // either the last node in the fragment or the last leaf in the fragment. 311 node = container->lastChild(); 312 while (node) { 313 if (isInterchangeNewlineNode(node)) { 314 m_hasInterchangeNewlineAtEnd = true; 315 removeNode(node); 316 break; 317 } 318 node = node->lastChild(); 319 } 320 321 node = container->firstChild(); 322 while (node) { 323 RefPtr<Node> next = NodeTraversal::next(node); 324 if (isInterchangeConvertedSpaceSpan(node)) { 325 next = NodeTraversal::nextSkippingChildren(node); 326 removeNodePreservingChildren(node); 327 } 328 node = next.get(); 329 } 330} 331 332inline void ReplaceSelectionCommand::InsertedNodes::respondToNodeInsertion(Node* node) 333{ 334 if (!node) 335 return; 336 337 if (!m_firstNodeInserted) 338 m_firstNodeInserted = node; 339 340 m_lastNodeInserted = node; 341} 342 343inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNodePreservingChildren(Node* node) 344{ 345 if (m_firstNodeInserted == node) 346 m_firstNodeInserted = NodeTraversal::next(node); 347 if (m_lastNodeInserted == node) 348 m_lastNodeInserted = node->lastChild() ? node->lastChild() : NodeTraversal::nextSkippingChildren(node); 349} 350 351inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNode(Node* node) 352{ 353 if (m_firstNodeInserted == node && m_lastNodeInserted == node) { 354 m_firstNodeInserted = 0; 355 m_lastNodeInserted = 0; 356 } else if (m_firstNodeInserted == node) 357 m_firstNodeInserted = NodeTraversal::nextSkippingChildren(m_firstNodeInserted.get()); 358 else if (m_lastNodeInserted == node) 359 m_lastNodeInserted = NodeTraversal::previousSkippingChildren(m_lastNodeInserted.get()); 360} 361 362inline void ReplaceSelectionCommand::InsertedNodes::didReplaceNode(Node* node, Node* newNode) 363{ 364 if (m_firstNodeInserted == node) 365 m_firstNodeInserted = newNode; 366 if (m_lastNodeInserted == node) 367 m_lastNodeInserted = newNode; 368} 369 370ReplaceSelectionCommand::ReplaceSelectionCommand(Document& document, PassRefPtr<DocumentFragment> fragment, CommandOptions options, EditAction editAction) 371 : CompositeEditCommand(document) 372 , m_selectReplacement(options & SelectReplacement) 373 , m_smartReplace(options & SmartReplace) 374 , m_matchStyle(options & MatchStyle) 375 , m_documentFragment(fragment) 376 , m_preventNesting(options & PreventNesting) 377 , m_movingParagraph(options & MovingParagraph) 378 , m_editAction(editAction) 379 , m_sanitizeFragment(options & SanitizeFragment) 380 , m_shouldMergeEnd(false) 381 , m_ignoreMailBlockquote(options & IgnoreMailBlockquote) 382{ 383} 384 385static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisiblePosition endOfInsertedContent) 386{ 387 Position existing = endOfExistingContent.deepEquivalent(); 388 Position inserted = endOfInsertedContent.deepEquivalent(); 389 bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote, CanCrossEditingBoundary); 390 return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted)); 391} 392 393bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMailBlockquote) 394{ 395 if (m_movingParagraph) 396 return false; 397 398 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); 399 VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBoundary); 400 if (prev.isNull()) 401 return false; 402 403 // When we have matching quote levels, its ok to merge more frequently. 404 // For a successful merge, we still need to make sure that the inserted content starts with the beginning of a paragraph. 405 // And we should only merge here if the selection start was inside a mail blockquote. This prevents against removing a 406 // blockquote from newly pasted quoted content that was pasted into an unquoted position. If that unquoted position happens 407 // to be right after another blockquote, we don't want to merge and risk stripping a valid block (and newline) from the pasted content. 408 if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMailBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent())) 409 return true; 410 411 return !selectionStartWasStartOfParagraph 412 && !fragmentHasInterchangeNewlineAtStart 413 && isStartOfParagraph(startOfInsertedContent) 414 && !startOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag) 415 && shouldMerge(startOfInsertedContent, prev); 416} 417 418bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) 419{ 420 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); 421 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); 422 if (next.isNull()) 423 return false; 424 425 return !selectionEndWasEndOfParagraph 426 && isEndOfParagraph(endOfInsertedContent) 427 && !endOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag) 428 && shouldMerge(endOfInsertedContent, next); 429} 430 431static bool isMailPasteAsQuotationNode(const Node* node) 432{ 433 return node && node->hasTagName(blockquoteTag) && node->isElementNode() && toElement(node)->getAttribute(classAttr) == ApplePasteAsQuotation; 434} 435 436static bool isHeaderElement(const Node* a) 437{ 438 if (!a) 439 return false; 440 441 return a->hasTagName(h1Tag) 442 || a->hasTagName(h2Tag) 443 || a->hasTagName(h3Tag) 444 || a->hasTagName(h4Tag) 445 || a->hasTagName(h5Tag) 446 || a->hasTagName(h6Tag); 447} 448 449static bool haveSameTagName(Node* a, Node* b) 450{ 451 return a && b && a->isElementNode() && b->isElementNode() && toElement(a)->tagName() == toElement(b)->tagName(); 452} 453 454bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const VisiblePosition& destination) 455{ 456 if (source.isNull() || destination.isNull()) 457 return false; 458 459 Node* sourceNode = source.deepEquivalent().deprecatedNode(); 460 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); 461 Node* sourceBlock = enclosingBlock(sourceNode); 462 Node* destinationBlock = enclosingBlock(destinationNode); 463 return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode) && 464 sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock)) && 465 enclosingListChild(sourceBlock) == enclosingListChild(destinationNode) && 466 enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent()) && 467 (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock)) && 468 // Don't merge to or from a position before or after a block because it would 469 // be a no-op and cause infinite recursion. 470 !isBlock(sourceNode) && !isBlock(destinationNode); 471} 472 473// Style rules that match just inserted elements could change their appearance, like 474// a div inserted into a document with div { display:inline; }. 475void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(InsertedNodes& insertedNodes) 476{ 477 RefPtr<Node> pastEndNode = insertedNodes.pastLastLeaf(); 478 RefPtr<Node> next; 479 for (RefPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) { 480 // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance 481 482 next = NodeTraversal::next(node.get()); 483 if (!node->isStyledElement()) 484 continue; 485 486 StyledElement* element = toStyledElement(node.get()); 487 488 const StyleProperties* inlineStyle = element->inlineStyle(); 489 RefPtr<EditingStyle> newInlineStyle = EditingStyle::create(inlineStyle); 490 if (inlineStyle) { 491 if (element->isHTMLElement()) { 492 Vector<QualifiedName> attributes; 493 HTMLElement* htmlElement = toHTMLElement(element); 494 495 if (newInlineStyle->conflictsWithImplicitStyleOfElement(htmlElement)) { 496 // e.g. <b style="font-weight: normal;"> is converted to <span style="font-weight: normal;"> 497 node = replaceElementWithSpanPreservingChildrenAndAttributes(htmlElement); 498 element = toStyledElement(node.get()); 499 insertedNodes.didReplaceNode(htmlElement, node.get()); 500 } else if (newInlineStyle->extractConflictingImplicitStyleOfAttributes(htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes, 501 EditingStyle::DoNotExtractMatchingStyle)) { 502 // e.g. <font size="3" style="font-size: 20px;"> is converted to <font style="font-size: 20px;"> 503 for (size_t i = 0; i < attributes.size(); i++) 504 removeNodeAttribute(element, attributes[i]); 505 } 506 } 507 508 ContainerNode* context = element->parentNode(); 509 510 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, 511 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. 512 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary); 513 if (blockquoteNode) 514 newInlineStyle->removeStyleFromRulesAndContext(element, document().documentElement()); 515 516 newInlineStyle->removeStyleFromRulesAndContext(element, context); 517 } 518 519 if (!inlineStyle || newInlineStyle->isEmpty()) { 520 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element) || isEmptyFontTag(element, AllowNonEmptyStyleAttribute)) { 521 insertedNodes.willRemoveNodePreservingChildren(element); 522 removeNodePreservingChildren(element); 523 continue; 524 } 525 removeNodeAttribute(element, styleAttr); 526 } else if (newInlineStyle->style()->propertyCount() != inlineStyle->propertyCount()) 527 setNodeAttribute(element, styleAttr, newInlineStyle->style()->asText()); 528 529 // FIXME: Tolerate differences in id, class, and style attributes. 530 if (isNonTableCellHTMLBlockElement(element) && areIdenticalElements(element, element->parentNode()) 531 && VisiblePosition(firstPositionInNode(element->parentNode())) == VisiblePosition(firstPositionInNode(element)) 532 && VisiblePosition(lastPositionInNode(element->parentNode())) == VisiblePosition(lastPositionInNode(element))) { 533 insertedNodes.willRemoveNodePreservingChildren(element); 534 removeNodePreservingChildren(element); 535 continue; 536 } 537 538 if (element->parentNode()->hasRichlyEditableStyle()) 539 removeNodeAttribute(element, contenteditableAttr); 540 541 // WebKit used to not add display: inline and float: none on copy. 542 // Keep this code around for backward compatibility 543 if (isLegacyAppleStyleSpan(element)) { 544 if (!element->firstChild()) { 545 insertedNodes.willRemoveNodePreservingChildren(element); 546 removeNodePreservingChildren(element); 547 continue; 548 } 549 // There are other styles that style rules can give to style spans, 550 // but these are the two important ones because they'll prevent 551 // inserted content from appearing in the right paragraph. 552 // FIXME: Hyatt is concerned that selectively using display:inline will give inconsistent 553 // results. We already know one issue because td elements ignore their display property 554 // in quirks mode (which Mail.app is always in). We should look for an alternative. 555 556 // Mutate using the CSSOM wrapper so we get the same event behavior as a script. 557 if (isBlock(element)) 558 element->style()->setPropertyInternal(CSSPropertyDisplay, "inline", false, IGNORE_EXCEPTION); 559 if (element->renderer() && element->renderer()->style().isFloating()) 560 element->style()->setPropertyInternal(CSSPropertyFloat, "none", false, IGNORE_EXCEPTION); 561 } 562 } 563} 564 565static bool isProhibitedParagraphChild(const AtomicString& name) 566{ 567 // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibited-paragraph-child 568 static NeverDestroyed<HashSet<AtomicString>> elements; 569 if (elements.get().isEmpty()) { 570 elements.get().add(addressTag.localName()); 571 elements.get().add(articleTag.localName()); 572 elements.get().add(asideTag.localName()); 573 elements.get().add(blockquoteTag.localName()); 574 elements.get().add(captionTag.localName()); 575 elements.get().add(centerTag.localName()); 576 elements.get().add(colTag.localName()); 577 elements.get().add(colgroupTag.localName()); 578 elements.get().add(ddTag.localName()); 579 elements.get().add(detailsTag.localName()); 580 elements.get().add(dirTag.localName()); 581 elements.get().add(divTag.localName()); 582 elements.get().add(dlTag.localName()); 583 elements.get().add(dtTag.localName()); 584 elements.get().add(fieldsetTag.localName()); 585 elements.get().add(figcaptionTag.localName()); 586 elements.get().add(figureTag.localName()); 587 elements.get().add(footerTag.localName()); 588 elements.get().add(formTag.localName()); 589 elements.get().add(h1Tag.localName()); 590 elements.get().add(h2Tag.localName()); 591 elements.get().add(h3Tag.localName()); 592 elements.get().add(h4Tag.localName()); 593 elements.get().add(h5Tag.localName()); 594 elements.get().add(h6Tag.localName()); 595 elements.get().add(headerTag.localName()); 596 elements.get().add(hgroupTag.localName()); 597 elements.get().add(hrTag.localName()); 598 elements.get().add(liTag.localName()); 599 elements.get().add(listingTag.localName()); 600 elements.get().add(mainTag.localName()); // Missing in the specification. 601 elements.get().add(menuTag.localName()); 602 elements.get().add(navTag.localName()); 603 elements.get().add(olTag.localName()); 604 elements.get().add(pTag.localName()); 605 elements.get().add(plaintextTag.localName()); 606 elements.get().add(preTag.localName()); 607 elements.get().add(sectionTag.localName()); 608 elements.get().add(summaryTag.localName()); 609 elements.get().add(tableTag.localName()); 610 elements.get().add(tbodyTag.localName()); 611 elements.get().add(tdTag.localName()); 612 elements.get().add(tfootTag.localName()); 613 elements.get().add(thTag.localName()); 614 elements.get().add(theadTag.localName()); 615 elements.get().add(trTag.localName()); 616 elements.get().add(ulTag.localName()); 617 elements.get().add(xmpTag.localName()); 618 } 619 return elements.get().contains(name); 620} 621 622void ReplaceSelectionCommand::makeInsertedContentRoundTrippableWithHTMLTreeBuilder(const InsertedNodes& insertedNodes) 623{ 624 RefPtr<Node> pastEndNode = insertedNodes.pastLastLeaf(); 625 RefPtr<Node> next; 626 for (RefPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) { 627 next = NodeTraversal::next(node.get()); 628 629 if (!node->isHTMLElement()) 630 continue; 631 632 if (isProhibitedParagraphChild(toHTMLElement(node.get())->localName())) { 633 if (auto* paragraphElement = enclosingElementWithTag(positionInParentBeforeNode(node.get()), pTag)) { 634 auto* parent = paragraphElement->parentNode(); 635 if (parent && parent->hasEditableStyle()) 636 moveNodeOutOfAncestor(node, paragraphElement); 637 } 638 } 639 640 if (isHeaderElement(node.get())) { 641 if (auto* headerElement = highestEnclosingNodeOfType(positionInParentBeforeNode(node.get()), isHeaderElement)) 642 moveNodeOutOfAncestor(node, headerElement); 643 } 644 } 645} 646 647void ReplaceSelectionCommand::moveNodeOutOfAncestor(PassRefPtr<Node> prpNode, PassRefPtr<Node> prpAncestor) 648{ 649 RefPtr<Node> node = prpNode; 650 RefPtr<Node> ancestor = prpAncestor; 651 652 VisiblePosition positionAtEndOfNode = lastPositionInOrAfterNode(node.get()); 653 VisiblePosition lastPositionInParagraph = lastPositionInNode(ancestor.get()); 654 if (positionAtEndOfNode == lastPositionInParagraph) { 655 removeNode(node); 656 if (ancestor->nextSibling()) 657 insertNodeBefore(node, ancestor->nextSibling()); 658 else 659 appendNode(node, ancestor->parentNode()); 660 } else { 661 RefPtr<Node> nodeToSplitTo = splitTreeToNode(node.get(), ancestor.get(), true); 662 removeNode(node); 663 insertNodeBefore(node, nodeToSplitTo); 664 } 665 if (!ancestor->firstChild()) 666 removeNode(ancestor.release()); 667} 668 669static inline bool hasRenderedText(const Text& text) 670{ 671 return text.renderer() && text.renderer()->hasRenderedText(); 672} 673 674void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& insertedNodes) 675{ 676 document().updateLayoutIgnorePendingStylesheets(); 677 678 Node* lastLeafInserted = insertedNodes.lastLeafInserted(); 679 if (lastLeafInserted && lastLeafInserted->isTextNode() && !hasRenderedText(toText(*lastLeafInserted)) 680 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), selectTag) 681 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), scriptTag)) { 682 insertedNodes.willRemoveNode(lastLeafInserted); 683 removeNode(lastLeafInserted); 684 } 685 686 // We don't have to make sure that firstNodeInserted isn't inside a select or script element 687 // because it is a top level node in the fragment and the user can't insert into those elements. 688 Node* firstNodeInserted = insertedNodes.firstNodeInserted(); 689 if (firstNodeInserted && firstNodeInserted->isTextNode() && !hasRenderedText(toText(*firstNodeInserted))) { 690 insertedNodes.willRemoveNode(firstNodeInserted); 691 removeNode(firstNodeInserted); 692 } 693} 694 695VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() const 696{ 697 // FIXME: Why is this hack here? What's special about <select> tags? 698 auto* enclosingSelect = enclosingElementWithTag(m_endOfInsertedContent, selectTag); 699 return enclosingSelect ? lastPositionInOrAfterNode(enclosingSelect) : m_endOfInsertedContent; 700} 701 702VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() const 703{ 704 return m_startOfInsertedContent; 705} 706 707static void removeHeadContents(ReplacementFragment& fragment) 708{ 709 if (fragment.isEmpty()) 710 return; 711 712 Vector<Element*> toRemove; 713 714 auto it = descendantsOfType<Element>(*fragment.fragment()).begin(); 715 auto end = descendantsOfType<Element>(*fragment.fragment()).end(); 716 while (it != end) { 717 if (it->hasTagName(baseTag) || it->hasTagName(linkTag) || it->hasTagName(metaTag) || it->hasTagName(styleTag) || isHTMLTitleElement(*it)) { 718 toRemove.append(&*it); 719 it.traverseNextSkippingChildren(); 720 continue; 721 } 722 ++it; 723 } 724 725 for (unsigned i = 0; i < toRemove.size(); ++i) 726 fragment.removeNode(toRemove[i]); 727} 728 729// Remove style spans before insertion if they are unnecessary. It's faster because we'll 730// avoid doing a layout. 731static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos) 732{ 733 Node* topNode = fragment.firstChild(); 734 735 // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans) 736 // and doesn't receive the optimization. 737 if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary)) 738 return false; 739 740 // Either there are no style spans in the fragment or a WebKit client has added content to the fragment 741 // before inserting it. Look for and handle style spans after insertion. 742 if (!isLegacyAppleStyleSpan(topNode)) 743 return false; 744 745 Node* wrappingStyleSpan = topNode; 746 RefPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create(insertionPos.parentAnchoredEquivalent()); 747 String styleText = styleAtInsertionPos->style()->asText(); 748 749 // FIXME: This string comparison is a naive way of comparing two styles. 750 // We should be taking the diff and check that the diff is empty. 751 if (styleText != toElement(wrappingStyleSpan)->getAttribute(styleAttr)) 752 return false; 753 754 fragment.removeNodePreservingChildren(wrappingStyleSpan); 755 return true; 756} 757 758// At copy time, WebKit wraps copied content in a span that contains the source document's 759// default styles. If the copied Range inherits any other styles from its ancestors, we put 760// those styles on a second span. 761// This function removes redundant styles from those spans, and removes the spans if all their 762// styles are redundant. 763// We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>. 764// We should remove styles from spans that are overridden by all of their children, either here 765// or at copy time. 766void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes) 767{ 768 HTMLElement* wrappingStyleSpan = 0; 769 // The style span that contains the source document's default style should be at 770 // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation), 771 // so search for the top level style span instead of assuming it's at the top. 772 for (Node* node = insertedNodes.firstNodeInserted(); node; node = NodeTraversal::next(node)) { 773 if (isLegacyAppleStyleSpan(node)) { 774 wrappingStyleSpan = toHTMLElement(node); 775 break; 776 } 777 } 778 779 // There might not be any style spans if we're pasting from another application or if 780 // we are here because of a document.execCommand("InsertHTML", ...) call. 781 if (!wrappingStyleSpan) 782 return; 783 784 RefPtr<EditingStyle> style = EditingStyle::create(wrappingStyleSpan->inlineStyle()); 785 ContainerNode* context = wrappingStyleSpan->parentNode(); 786 787 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, 788 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. 789 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary); 790 if (blockquoteNode) 791 context = document().documentElement(); 792 793 // This operation requires that only editing styles to be removed from sourceDocumentStyle. 794 style->prepareToApplyAt(firstPositionInNode(context)); 795 796 // Remove block properties in the span's style. This prevents properties that probably have no effect 797 // currently from affecting blocks later if the style is cloned for a new block element during a future 798 // editing operation. 799 // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked 800 // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might. 801 style->removeBlockProperties(); 802 803 if (style->isEmpty() || !wrappingStyleSpan->firstChild()) { 804 insertedNodes.willRemoveNodePreservingChildren(wrappingStyleSpan); 805 removeNodePreservingChildren(wrappingStyleSpan); 806 } else 807 setNodeAttribute(wrappingStyleSpan, styleAttr, style->style()->asText()); 808} 809 810void ReplaceSelectionCommand::mergeEndIfNeeded() 811{ 812 if (!m_shouldMergeEnd) 813 return; 814 815 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); 816 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); 817 818 // Bail to avoid infinite recursion. 819 if (m_movingParagraph) { 820 ASSERT_NOT_REACHED(); 821 return; 822 } 823 824 // Merging two paragraphs will destroy the moved one's block styles. Always move the end of inserted forward 825 // to preserve the block style of the paragraph already in the document, unless the paragraph to move would 826 // include the what was the start of the selection that was pasted into, so that we preserve that paragraph's 827 // block styles. 828 bool mergeForward = !(inSameParagraph(startOfInsertedContent, endOfInsertedContent) && !isStartOfParagraph(startOfInsertedContent)); 829 830 VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent; 831 VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next(); 832 833 // Merging forward could result in deleting the destination anchor node. 834 // To avoid this, we add a placeholder node before the start of the paragraph. 835 if (endOfParagraph(startOfParagraphToMove) == destination) { 836 RefPtr<Node> placeholder = createBreakElement(document()); 837 insertNodeBefore(placeholder, startOfParagraphToMove.deepEquivalent().deprecatedNode()); 838 destination = VisiblePosition(positionBeforeNode(placeholder.get())); 839 } 840 841 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination); 842 843 // Merging forward will remove m_endOfInsertedContent from the document. 844 if (mergeForward) { 845 if (m_startOfInsertedContent.isOrphan()) 846 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent(); 847 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent(); 848 // If we merged text nodes, m_endOfInsertedContent could be null. If this is the case, we use m_startOfInsertedContent. 849 if (m_endOfInsertedContent.isNull()) 850 m_endOfInsertedContent = m_startOfInsertedContent; 851 } 852} 853 854static Node* enclosingInline(Node* node) 855{ 856 while (ContainerNode* parent = node->parentNode()) { 857 if (isBlockFlowElement(parent) || parent->hasTagName(bodyTag)) 858 return node; 859 // Stop if any previous sibling is a block. 860 for (Node* sibling = node->previousSibling(); sibling; sibling = sibling->previousSibling()) { 861 if (isBlockFlowElement(sibling)) 862 return node; 863 } 864 node = parent; 865 } 866 return node; 867} 868 869static bool isInlineNodeWithStyle(const Node* node) 870{ 871 // We don't want to skip over any block elements. 872 if (isBlock(node)) 873 return false; 874 875 if (!node->isHTMLElement()) 876 return false; 877 878 // We can skip over elements whose class attribute is 879 // one of our internal classes. 880 const HTMLElement* element = static_cast<const HTMLElement*>(node); 881 const AtomicString& classAttributeValue = element->getAttribute(classAttr); 882 if (classAttributeValue == AppleTabSpanClass 883 || classAttributeValue == AppleConvertedSpace 884 || classAttributeValue == ApplePasteAsQuotation) 885 return true; 886 887 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(element); 888} 889 890inline Node* nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(const Position& insertionPos) 891{ 892 Node* containgBlock = enclosingBlock(insertionPos.containerNode()); 893 return highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle, CannotCrossEditingBoundary, containgBlock); 894} 895 896void ReplaceSelectionCommand::doApply() 897{ 898 VisibleSelection selection = endingSelection(); 899 ASSERT(selection.isCaretOrRange()); 900 ASSERT(selection.start().deprecatedNode()); 901 if (!selection.isNonOrphanedCaretOrRange() || !selection.start().deprecatedNode()) 902 return; 903 904 if (!selection.rootEditableElement()) 905 return; 906 907#if PLATFORM(IOS) 908 // In plain text only regions, we create style-less fragments, so the inserted content will automatically 909 // match the style of the surrounding area and so we can avoid unnecessary work below for m_matchStyle. 910 if (!selection.isContentRichlyEditable()) 911 m_matchStyle = false; 912#endif 913 914 ReplacementFragment fragment(document(), m_documentFragment.get(), selection); 915 if (performTrivialReplace(fragment)) 916 return; 917 918 // We can skip matching the style if the selection is plain text. 919 if ((selection.start().deprecatedNode()->renderer() && selection.start().deprecatedNode()->renderer()->style().userModify() == READ_WRITE_PLAINTEXT_ONLY) 920 && (selection.end().deprecatedNode()->renderer() && selection.end().deprecatedNode()->renderer()->style().userModify() == READ_WRITE_PLAINTEXT_ONLY)) 921 m_matchStyle = false; 922 923 if (m_matchStyle) { 924 m_insertionStyle = EditingStyle::create(selection.start()); 925 m_insertionStyle->mergeTypingStyle(document()); 926 } 927 928 VisiblePosition visibleStart = selection.visibleStart(); 929 VisiblePosition visibleEnd = selection.visibleEnd(); 930 931 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd); 932 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart); 933 934 Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode()); 935 936 Position insertionPos = selection.start(); 937 bool shouldHandleMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary) && !m_ignoreMailBlockquote; 938 bool selectionIsPlainText = !selection.isContentRichlyEditable(); 939 Element* currentRoot = selection.rootEditableElement(); 940 941 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !shouldHandleMailBlockquote) 942 || startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText) 943 m_preventNesting = false; 944 945 if (selection.isRange()) { 946 // When the end of the selection being pasted into is at the end of a paragraph, and that selection 947 // spans multiple blocks, not merging may leave an empty line. 948 // When the start of the selection being pasted into is at the start of a block, not merging 949 // will leave hanging block(s). 950 // Merge blocks if the start of the selection was in a Mail blockquote, since we handle 951 // that case specially to prevent nesting. 952 bool mergeBlocksAfterDelete = shouldHandleMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart); 953 // FIXME: We should only expand to include fully selected special elements if we are copying a 954 // selection and pasting it on top of itself. 955 deleteSelection(false, mergeBlocksAfterDelete, true, false); 956 visibleStart = endingSelection().visibleStart(); 957 if (fragment.hasInterchangeNewlineAtStart()) { 958 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { 959 if (!isEndOfEditableOrNonEditableContent(visibleStart)) 960 setEndingSelection(visibleStart.next()); 961 } else 962 insertParagraphSeparator(); 963 } 964 insertionPos = endingSelection().start(); 965 } else { 966 ASSERT(selection.isCaret()); 967 if (fragment.hasInterchangeNewlineAtStart()) { 968 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary); 969 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull()) 970 setEndingSelection(next); 971 else { 972 insertParagraphSeparator(); 973 visibleStart = endingSelection().visibleStart(); 974 } 975 } 976 // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block. 977 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret. 978 // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>, 979 // not <div>xbar<div>bar</div><div>bazx</div></div>. 980 // Don't do this if the selection started in a Mail blockquote. 981 if (m_preventNesting && !shouldHandleMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { 982 insertParagraphSeparator(); 983 setEndingSelection(endingSelection().visibleStart().previous()); 984 } 985 insertionPos = endingSelection().start(); 986 } 987 988 // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break 989 // out of any surrounding Mail blockquotes. Unless we're inserting in a table, in which case 990 // breaking the blockquote will prevent the content from actually being inserted in the table. 991 if (shouldHandleMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) { 992 applyCommandToComposite(BreakBlockquoteCommand::create(document())); 993 // This will leave a br between the split. 994 Node* br = endingSelection().start().deprecatedNode(); 995 ASSERT(br->hasTagName(brTag)); 996 // Insert content between the two blockquotes, but remove the br (since it was just a placeholder). 997 insertionPos = positionInParentBeforeNode(br); 998 removeNode(br); 999 } 1000 1001 // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world. 1002 prepareWhitespaceAtPositionForSplit(insertionPos); 1003 1004 // If the downstream node has been removed there's no point in continuing. 1005 if (!insertionPos.downstream().deprecatedNode()) 1006 return; 1007 1008 // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after 1009 // 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 1010 // away, there are positions after the br which map to the same visible position as [br, 0]). 1011 RefPtr<Node> endBR = insertionPos.downstream().deprecatedNode()->hasTagName(brTag) ? insertionPos.downstream().deprecatedNode() : nullptr; 1012 VisiblePosition originalVisPosBeforeEndBR; 1013 if (endBR) 1014 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR.get()), DOWNSTREAM).previous(); 1015 1016 RefPtr<Node> insertionBlock = enclosingBlock(insertionPos.deprecatedNode()); 1017 1018 // Adjust insertionPos to prevent nesting. 1019 // If the start was in a Mail blockquote, we will have already handled adjusting insertionPos above. 1020 if (m_preventNesting && insertionBlock && !isTableCell(insertionBlock.get()) && !shouldHandleMailBlockquote) { 1021 ASSERT(insertionBlock != currentRoot); 1022 VisiblePosition visibleInsertionPos(insertionPos); 1023 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd())) 1024 insertionPos = positionInParentAfterNode(insertionBlock.get()); 1025 else if (isStartOfBlock(visibleInsertionPos)) 1026 insertionPos = positionInParentBeforeNode(insertionBlock.get()); 1027 } 1028 1029 // Paste at start or end of link goes outside of link. 1030 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos); 1031 1032 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be 1033 // any work performed after this that queries or uses the typing style. 1034 frame().selection().clearTypingStyle(); 1035 1036 removeHeadContents(fragment); 1037 1038 // We don't want the destination to end up inside nodes that weren't selected. To avoid that, we move the 1039 // position forward without changing the visible position so we're still at the same visible location, but 1040 // outside of preceding tags. 1041 insertionPos = positionAvoidingPrecedingNodes(insertionPos); 1042 1043 // Paste into run of tabs splits the tab span. 1044 insertionPos = positionOutsideTabSpan(insertionPos); 1045 1046 bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos); 1047 1048 // We're finished if there is nothing to add. 1049 if (fragment.isEmpty() || !fragment.firstChild()) 1050 return; 1051 1052 // If we are not trying to match the destination style we prefer a position 1053 // that is outside inline elements that provide style. 1054 // This way we can produce a less verbose markup. 1055 // We can skip this optimization for fragments not wrapped in one of 1056 // our style spans and for positions inside list items 1057 // since insertAsListItems already does the right thing. 1058 if (!m_matchStyle && !enclosingList(insertionPos.containerNode())) { 1059 if (insertionPos.containerNode()->isTextNode() && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) { 1060 splitTextNode(insertionPos.containerText(), insertionPos.offsetInContainerNode()); 1061 insertionPos = firstPositionInNode(insertionPos.containerNode()); 1062 } 1063 1064 if (RefPtr<Node> nodeToSplitTo = nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(insertionPos)) { 1065 if (insertionPos.containerNode() != nodeToSplitTo->parentNode()) { 1066 Node* splitStart = insertionPos.computeNodeAfterPosition(); 1067 if (!splitStart) 1068 splitStart = insertionPos.containerNode(); 1069 nodeToSplitTo = splitTreeToNode(splitStart, nodeToSplitTo->parentNode()).get(); 1070 insertionPos = positionInParentBeforeNode(nodeToSplitTo.get()); 1071 } 1072 } 1073 } 1074 1075 // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try 1076 // again here if they've been removed. 1077 1078 // 1) Insert the content. 1079 // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>. 1080 // 3) Merge the start of the added content with the content before the position being pasted into. 1081 // 4) Do one of the following: a) expand the last br if the fragment ends with one and it collapsed, 1082 // b) merge the last paragraph of the incoming fragment with the paragraph that contained the 1083 // end of the selection that was pasted into, or c) handle an interchange newline at the end of the 1084 // incoming fragment. 1085 // 5) Add spaces for smart replace. 1086 // 6) Select the replacement if requested, and match style if requested. 1087 1088 InsertedNodes insertedNodes; 1089 RefPtr<Node> refNode = fragment.firstChild(); 1090 RefPtr<Node> node = refNode->nextSibling(); 1091 1092 fragment.removeNode(refNode); 1093 1094 Node* blockStart = enclosingBlock(insertionPos.deprecatedNode()); 1095 if ((isListElement(refNode.get()) || (isLegacyAppleStyleSpan(refNode.get()) && isListElement(refNode->firstChild()))) 1096 && blockStart && blockStart->renderer()->isListItem()) 1097 refNode = insertAsListItems(toHTMLElement(refNode.get()), blockStart, insertionPos, insertedNodes); 1098 else { 1099 insertNodeAt(refNode, insertionPos); 1100 insertedNodes.respondToNodeInsertion(refNode.get()); 1101 } 1102 1103 // Mutation events (bug 22634) may have already removed the inserted content 1104 if (!refNode->inDocument()) 1105 return; 1106 1107 bool plainTextFragment = isPlainTextMarkup(refNode.get()); 1108 1109 while (node) { 1110 RefPtr<Node> next = node->nextSibling(); 1111 fragment.removeNode(node.get()); 1112 insertNodeAfter(node, refNode.get()); 1113 insertedNodes.respondToNodeInsertion(node.get()); 1114 1115 // Mutation events (bug 22634) may have already removed the inserted content 1116 if (!node->inDocument()) 1117 return; 1118 1119 refNode = node; 1120 if (node && plainTextFragment) 1121 plainTextFragment = isPlainTextMarkup(node.get()); 1122 node = next; 1123 } 1124 1125 removeUnrenderedTextNodesAtEnds(insertedNodes); 1126 1127 if (!handledStyleSpans) 1128 handleStyleSpans(insertedNodes); 1129 1130 // Mutation events (bug 20161) may have already removed the inserted content 1131 if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted()->inDocument()) 1132 return; 1133 1134 VisiblePosition startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted()); 1135 1136 // We inserted before the insertionBlock to prevent nesting, and the content before the insertionBlock wasn't in its own block and 1137 // didn't have a br after it, so the inserted content ended up in the same paragraph. 1138 if (insertionBlock && insertionPos.deprecatedNode() == insertionBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < insertionBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent)) 1139 insertNodeAt(createBreakElement(document()), startOfInsertedContent.deepEquivalent()); 1140 1141 if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR.get(), originalVisPosBeforeEndBR))) { 1142 RefPtr<Node> parent = endBR->parentNode(); 1143 insertedNodes.willRemoveNode(endBR.get()); 1144 removeNode(endBR); 1145 if (Node* nodeToRemove = highestNodeToRemoveInPruning(parent.get())) { 1146 insertedNodes.willRemoveNode(nodeToRemove); 1147 removeNode(nodeToRemove); 1148 } 1149 } 1150 1151 makeInsertedContentRoundTrippableWithHTMLTreeBuilder(insertedNodes); 1152 1153 removeRedundantStylesAndKeepStyleSpanInline(insertedNodes); 1154 1155 if (m_sanitizeFragment) 1156 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insertedNodes.firstNodeInserted(), insertedNodes.pastLastLeaf())); 1157 1158 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes. 1159 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted()); 1160 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafInserted()); 1161 1162 // Determine whether or not we should merge the end of inserted content with what's after it before we do 1163 // the start merge so that the start merge doesn't effect our decision. 1164 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph); 1165 1166 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), shouldHandleMailBlockquote)) { 1167 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedContent(); 1168 VisiblePosition destination = startOfParagraphToMove.previous(); 1169 // We need to handle the case where we need to merge the end 1170 // but our destination node is inside an inline that is the last in the block. 1171 // We insert a placeholder before the newly inserted content to avoid being merged into the inline. 1172 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); 1173 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNode) && enclosingInline(destinationNode)->nextSibling()) 1174 insertNodeBefore(createBreakElement(document()), refNode.get()); 1175 1176 // Merging the the first paragraph of inserted content with the content that came 1177 // before the selection that was pasted into would also move content after 1178 // the selection that was pasted into if: only one paragraph was being pasted, 1179 // and it was not wrapped in a block, the selection that was pasted into ended 1180 // at the end of a block and the next paragraph didn't start at the start of a block. 1181 // Insert a line break just after the inserted content to separate it from what 1182 // comes after and prevent that from happening. 1183 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent(); 1184 if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) { 1185 insertNodeAt(createBreakElement(document()), endOfInsertedContent.deepEquivalent()); 1186 // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move 1187 if (!startOfParagraphToMove.deepEquivalent().anchorNode()->inDocument()) 1188 return; 1189 } 1190 1191 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are 1192 // only ever used to create positions where inserted content starts/ends. 1193 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination); 1194 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent().downstream(); 1195 if (m_endOfInsertedContent.isOrphan()) 1196 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent().upstream(); 1197 } 1198 1199 Position lastPositionToSelect; 1200 if (fragment.hasInterchangeNewlineAtEnd()) { 1201 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent(); 1202 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); 1203 1204 if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) { 1205 if (!isStartOfParagraph(endOfInsertedContent)) { 1206 setEndingSelection(endOfInsertedContent); 1207 Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEquivalent().deprecatedNode()); 1208 if (isListItem(enclosingNode)) { 1209 RefPtr<Node> newListItem = createListItemElement(document()); 1210 insertNodeAfter(newListItem, enclosingNode); 1211 setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.get()))); 1212 } else { 1213 // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph 1214 // block's style seems to annoy users. 1215 insertParagraphSeparator(true, !shouldHandleMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(), 1216 isMailBlockquote, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode())); 1217 } 1218 1219 // Select up to the paragraph separator that was added. 1220 lastPositionToSelect = endingSelection().visibleStart().deepEquivalent(); 1221 updateNodesInserted(lastPositionToSelect.deprecatedNode()); 1222 } 1223 } else { 1224 // Select up to the beginning of the next paragraph. 1225 lastPositionToSelect = next.deepEquivalent().downstream(); 1226 } 1227 1228 } else 1229 mergeEndIfNeeded(); 1230 1231 if (Node* mailBlockquote = enclosingNodeOfType(positionAtStartOfInsertedContent().deepEquivalent(), isMailPasteAsQuotationNode)) 1232 removeNodeAttribute(toElement(mailBlockquote), classAttr); 1233 1234 if (shouldPerformSmartReplace()) 1235 addSpacesForSmartReplace(); 1236 1237 // If we are dealing with a fragment created from plain text 1238 // no style matching is necessary. 1239 if (plainTextFragment) 1240 m_matchStyle = false; 1241 1242 completeHTMLReplacement(lastPositionToSelect); 1243} 1244 1245bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR) 1246{ 1247 if (!endBR || !endBR->inDocument()) 1248 return false; 1249 1250 VisiblePosition visiblePos(positionBeforeNode(endBR)); 1251 1252 // Don't remove the br if nothing was inserted. 1253 if (visiblePos.previous() == originalVisPosBeforeEndBR) 1254 return false; 1255 1256 // Remove the br if it is collapsed away and so is unnecessary. 1257 if (!document().inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos)) 1258 return true; 1259 1260 // A br that was originally holding a line open should be displaced by inserted content or turned into a line break. 1261 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder. 1262 return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos); 1263} 1264 1265bool ReplaceSelectionCommand::shouldPerformSmartReplace() const 1266{ 1267 if (!m_smartReplace) 1268 return false; 1269 1270 Element* textControl = enclosingTextFormControl(positionAtStartOfInsertedContent().deepEquivalent()); 1271 if (textControl && isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isPasswordField()) 1272 return false; // Disable smart replace for password fields. 1273 1274 return true; 1275} 1276 1277static bool isCharacterSmartReplaceExemptConsideringNonBreakingSpace(UChar32 character, bool previousCharacter) 1278{ 1279 return isCharacterSmartReplaceExempt(character == noBreakSpace ? ' ' : character, previousCharacter); 1280} 1281 1282void ReplaceSelectionCommand::addSpacesForSmartReplace() 1283{ 1284 VisiblePosition startOfInsertedContent = positionAtStartOfInsertedContent(); 1285 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent(); 1286 1287 Position endUpstream = endOfInsertedContent.deepEquivalent().upstream(); 1288 Node* endNode = endUpstream.computeNodeBeforePosition(); 1289 int endOffset = endNode && endNode->isTextNode() ? toText(endNode)->length() : 0; 1290 if (endUpstream.anchorType() == Position::PositionIsOffsetInAnchor) { 1291 endNode = endUpstream.containerNode(); 1292 endOffset = endUpstream.offsetInContainerNode(); 1293 } 1294 1295 bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(endOfInsertedContent.characterAfter(), false); 1296 if (needsTrailingSpace && endNode) { 1297 bool collapseWhiteSpace = !endNode->renderer() || endNode->renderer()->style().collapseWhiteSpace(); 1298 if (endNode->isTextNode()) { 1299 insertTextIntoNode(toText(endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); 1300 if (m_endOfInsertedContent.containerNode() == endNode) 1301 m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1); 1302 } else { 1303 RefPtr<Node> node = document().createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " "); 1304 insertNodeAfter(node, endNode); 1305 updateNodesInserted(node.get()); 1306 } 1307 } 1308 1309 document().updateLayout(); 1310 1311 Position startDownstream = startOfInsertedContent.deepEquivalent().downstream(); 1312 Node* startNode = startDownstream.computeNodeAfterPosition(); 1313 unsigned startOffset = 0; 1314 if (startDownstream.anchorType() == Position::PositionIsOffsetInAnchor) { 1315 startNode = startDownstream.containerNode(); 1316 startOffset = startDownstream.offsetInContainerNode(); 1317 } 1318 1319 bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(startOfInsertedContent.previous().characterAfter(), true); 1320 if (needsLeadingSpace && startNode) { 1321 bool collapseWhiteSpace = !startNode->renderer() || startNode->renderer()->style().collapseWhiteSpace(); 1322 if (startNode->isTextNode()) { 1323 insertTextIntoNode(toText(startNode), startOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " "); 1324 if (m_endOfInsertedContent.containerNode() == startNode && m_endOfInsertedContent.offsetInContainerNode()) 1325 m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1); 1326 } else { 1327 RefPtr<Node> node = document().createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " "); 1328 // Don't updateNodesInserted. Doing so would set m_endOfInsertedContent to be the node containing the leading space, 1329 // but m_endOfInsertedContent is supposed to mark the end of pasted content. 1330 insertNodeBefore(node, startNode); 1331 m_startOfInsertedContent = firstPositionInNode(node.get()); 1332 } 1333 } 1334} 1335 1336void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect) 1337{ 1338 Position start = positionAtStartOfInsertedContent().deepEquivalent(); 1339 Position end = positionAtEndOfInsertedContent().deepEquivalent(); 1340 1341 // Mutation events may have deleted start or end 1342 if (start.isNotNull() && !start.isOrphan() && end.isNotNull() && !end.isOrphan()) { 1343 // FIXME (11475): Remove this and require that the creator of the fragment to use nbsps. 1344 rebalanceWhitespaceAt(start); 1345 rebalanceWhitespaceAt(end); 1346 1347 if (m_matchStyle) { 1348 ASSERT(m_insertionStyle); 1349 applyStyle(m_insertionStyle.get(), start, end); 1350 } 1351 1352 if (lastPositionToSelect.isNotNull()) 1353 end = lastPositionToSelect; 1354 1355 mergeTextNodesAroundPosition(start, end); 1356 mergeTextNodesAroundPosition(end, start); 1357 } else if (lastPositionToSelect.isNotNull()) 1358 start = end = lastPositionToSelect; 1359 else 1360 return; 1361 1362 if (m_selectReplacement) 1363 setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY, endingSelection().isDirectional())); 1364 else 1365 setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY, endingSelection().isDirectional())); 1366} 1367 1368void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, Position& positionOnlyToBeUpdated) 1369{ 1370 bool positionIsOffsetInAnchor = position.anchorType() == Position::PositionIsOffsetInAnchor; 1371 bool positionOnlyToBeUpdatedIsOffsetInAnchor = positionOnlyToBeUpdated.anchorType() == Position::PositionIsOffsetInAnchor; 1372 RefPtr<Text> text = 0; 1373 if (positionIsOffsetInAnchor && position.containerNode() && position.containerNode()->isTextNode()) 1374 text = toText(position.containerNode()); 1375 else { 1376 Node* before = position.computeNodeBeforePosition(); 1377 if (before && before->isTextNode()) 1378 text = toText(before); 1379 else { 1380 Node* after = position.computeNodeAfterPosition(); 1381 if (after && after->isTextNode()) 1382 text = toText(after); 1383 } 1384 } 1385 if (!text) 1386 return; 1387 1388 if (text->previousSibling() && text->previousSibling()->isTextNode()) { 1389 RefPtr<Text> previous = toText(text->previousSibling()); 1390 insertTextIntoNode(text, 0, previous->data()); 1391 1392 if (positionIsOffsetInAnchor) 1393 position.moveToOffset(previous->length() + position.offsetInContainerNode()); 1394 else 1395 updatePositionForNodeRemoval(position, previous.get()); 1396 1397 if (positionOnlyToBeUpdatedIsOffsetInAnchor) { 1398 if (positionOnlyToBeUpdated.containerNode() == text) 1399 positionOnlyToBeUpdated.moveToOffset(previous->length() + positionOnlyToBeUpdated.offsetInContainerNode()); 1400 else if (positionOnlyToBeUpdated.containerNode() == previous) 1401 positionOnlyToBeUpdated.moveToPosition(text, positionOnlyToBeUpdated.offsetInContainerNode()); 1402 } else 1403 updatePositionForNodeRemoval(positionOnlyToBeUpdated, previous.get()); 1404 1405 removeNode(previous); 1406 } 1407 if (text->nextSibling() && text->nextSibling()->isTextNode()) { 1408 RefPtr<Text> next = toText(text->nextSibling()); 1409 unsigned originalLength = text->length(); 1410 insertTextIntoNode(text, originalLength, next->data()); 1411 1412 if (!positionIsOffsetInAnchor) 1413 updatePositionForNodeRemoval(position, next.get()); 1414 1415 if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.containerNode() == next) 1416 positionOnlyToBeUpdated.moveToPosition(text, originalLength + positionOnlyToBeUpdated.offsetInContainerNode()); 1417 else 1418 updatePositionForNodeRemoval(positionOnlyToBeUpdated, next.get()); 1419 1420 removeNode(next); 1421 } 1422} 1423 1424EditAction ReplaceSelectionCommand::editingAction() const 1425{ 1426 return m_editAction; 1427} 1428 1429// If the user is inserting a list into an existing list, instead of nesting the list, 1430// we put the list items into the existing list. 1431Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<HTMLElement> prpListElement, Node* insertionBlock, const Position& insertPos, InsertedNodes& insertedNodes) 1432{ 1433 RefPtr<HTMLElement> listElement = prpListElement; 1434 1435 while (listElement->hasChildNodes() && isListElement(listElement->firstChild()) && listElement->childNodeCount() == 1) 1436 listElement = toHTMLElement(listElement->firstChild()); 1437 1438 bool isStart = isStartOfParagraph(insertPos); 1439 bool isEnd = isEndOfParagraph(insertPos); 1440 bool isMiddle = !isStart && !isEnd; 1441 Node* lastNode = insertionBlock; 1442 1443 // If we're in the middle of a list item, we should split it into two separate 1444 // list items and insert these nodes between them. 1445 if (isMiddle) { 1446 int textNodeOffset = insertPos.offsetInContainerNode(); 1447 if (insertPos.deprecatedNode()->isTextNode() && textNodeOffset > 0) 1448 splitTextNode(toText(insertPos.deprecatedNode()), textNodeOffset); 1449 splitTreeToNode(insertPos.deprecatedNode(), lastNode, true); 1450 } 1451 1452 while (RefPtr<Node> listItem = listElement->firstChild()) { 1453 listElement->removeChild(listItem.get(), ASSERT_NO_EXCEPTION); 1454 if (isStart || isMiddle) { 1455 insertNodeBefore(listItem, lastNode); 1456 insertedNodes.respondToNodeInsertion(listItem.get()); 1457 } else if (isEnd) { 1458 insertNodeAfter(listItem, lastNode); 1459 insertedNodes.respondToNodeInsertion(listItem.get()); 1460 lastNode = listItem.get(); 1461 } else 1462 ASSERT_NOT_REACHED(); 1463 } 1464 if (isStart || isMiddle) 1465 lastNode = lastNode->previousSibling(); 1466 return lastNode; 1467} 1468 1469void ReplaceSelectionCommand::updateNodesInserted(Node *node) 1470{ 1471 if (!node) 1472 return; 1473 1474 if (m_startOfInsertedContent.isNull()) 1475 m_startOfInsertedContent = firstPositionInOrBeforeNode(node); 1476 1477 m_endOfInsertedContent = lastPositionInOrAfterNode(node->lastDescendant()); 1478} 1479 1480// During simple pastes, where we're just pasting a text node into a run of text, we insert the text node 1481// directly into the text node that holds the selection. This is much faster than the generalized code in 1482// ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.cgi?id=6148> since we don't 1483// split text nodes. 1484bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& fragment) 1485{ 1486 if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode()) 1487 return false; 1488 1489 // FIXME: Would be nice to handle smart replace in the fast path. 1490 if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd()) 1491 return false; 1492 1493 // e.g. when "bar" is inserted after "foo" in <div><u>foo</u></div>, "bar" should not be underlined. 1494 if (nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(endingSelection().start())) 1495 return false; 1496 1497 RefPtr<Node> nodeAfterInsertionPos = endingSelection().end().downstream().anchorNode(); 1498 Text* textNode = toText(fragment.firstChild()); 1499 // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here. 1500 1501 Position start = endingSelection().start(); 1502 Position end = replaceSelectedTextInNode(textNode->data()); 1503 if (end.isNull()) 1504 return false; 1505 1506 if (nodeAfterInsertionPos && nodeAfterInsertionPos->parentNode() && nodeAfterInsertionPos->hasTagName(brTag) 1507 && shouldRemoveEndBR(nodeAfterInsertionPos.get(), positionBeforeNode(nodeAfterInsertionPos.get()))) 1508 removeNodeAndPruneAncestors(nodeAfterInsertionPos.get()); 1509 1510 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end); 1511 1512 setEndingSelection(selectionAfterReplace); 1513 1514 return true; 1515} 1516 1517} // namespace WebCore 1518