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