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