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