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