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