| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. | 2 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved. | 3 * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved. |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
| 7 * are met: | 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 371 , m_matchStyle(options & MatchStyle) | 371 , m_matchStyle(options & MatchStyle) |
| 372 , m_documentFragment(fragment) | 372 , m_documentFragment(fragment) |
| 373 , m_preventNesting(options & PreventNesting) | 373 , m_preventNesting(options & PreventNesting) |
| 374 , m_movingParagraph(options & MovingParagraph) | 374 , m_movingParagraph(options & MovingParagraph) |
| 375 , m_editAction(editAction) | 375 , m_editAction(editAction) |
| 376 , m_sanitizeFragment(options & SanitizeFragment) | 376 , m_sanitizeFragment(options & SanitizeFragment) |
| 377 , m_shouldMergeEnd(false) | 377 , m_shouldMergeEnd(false) |
| 378 { | 378 { |
| 379 } | 379 } |
| 380 | 380 |
| 381 static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisibleP
osition endOfInsertedContent) | 381 bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara
graph, bool fragmentHasInterchangeNewlineAtStart) |
| 382 { | |
| 383 Position existing = endOfExistingContent.deepEquivalent(); | |
| 384 Position inserted = endOfInsertedContent.deepEquivalent(); | |
| 385 bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote
, CanCrossEditingBoundary); | |
| 386 return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == n
umEnclosingMailBlockquotes(inserted)); | |
| 387 } | |
| 388 | |
| 389 bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara
graph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMa
ilBlockquote) | |
| 390 { | 382 { |
| 391 if (m_movingParagraph) | 383 if (m_movingParagraph) |
| 392 return false; | 384 return false; |
| 393 | 385 |
| 394 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); | 386 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); |
| 395 VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBou
ndary); | 387 VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBou
ndary); |
| 396 if (prev.isNull()) | 388 if (prev.isNull()) |
| 397 return false; | 389 return false; |
| 398 | |
| 399 // When we have matching quote levels, its ok to merge more frequently. | |
| 400 // For a successful merge, we still need to make sure that the inserted cont
ent starts with the beginning of a paragraph. | |
| 401 // And we should only merge here if the selection start was inside a mail bl
ockquote. This prevents against removing a | |
| 402 // blockquote from newly pasted quoted content that was pasted into an unquo
ted position. If that unquoted position happens | |
| 403 // to be right after another blockquote, we don't want to merge and risk str
ipping a valid block (and newline) from the pasted content. | |
| 404 if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMai
lBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent())) | |
| 405 return true; | |
| 406 | 390 |
| 407 return !selectionStartWasStartOfParagraph | 391 return !selectionStartWasStartOfParagraph |
| 408 && !fragmentHasInterchangeNewlineAtStart | 392 && !fragmentHasInterchangeNewlineAtStart |
| 409 && isStartOfParagraph(startOfInsertedContent) | 393 && isStartOfParagraph(startOfInsertedContent) |
| 410 && !startOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName
(brTag) | 394 && !startOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName
(brTag) |
| 411 && shouldMerge(startOfInsertedContent, prev); | 395 && shouldMerge(startOfInsertedContent, prev); |
| 412 } | 396 } |
| 413 | 397 |
| 414 bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) | 398 bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) |
| 415 { | 399 { |
| 416 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); | 400 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); |
| 417 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary)
; | 401 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary)
; |
| 418 if (next.isNull()) | 402 if (next.isNull()) |
| 419 return false; | 403 return false; |
| 420 | 404 |
| 421 return !selectionEndWasEndOfParagraph | 405 return !selectionEndWasEndOfParagraph |
| 422 && isEndOfParagraph(endOfInsertedContent) | 406 && isEndOfParagraph(endOfInsertedContent) |
| 423 && !endOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(b
rTag) | 407 && !endOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(b
rTag) |
| 424 && shouldMerge(endOfInsertedContent, next); | 408 && shouldMerge(endOfInsertedContent, next); |
| 425 } | 409 } |
| 426 | 410 |
| 427 static bool isMailPasteAsQuotationNode(const Node* node) | |
| 428 { | |
| 429 return node && node->hasTagName(blockquoteTag) && node->isElementNode() && t
oElement(node)->getAttribute(classAttr) == ApplePasteAsQuotation; | |
| 430 } | |
| 431 | |
| 432 static bool isHeaderElement(const Node* a) | 411 static bool isHeaderElement(const Node* a) |
| 433 { | 412 { |
| 434 if (!a) | 413 if (!a) |
| 435 return false; | 414 return false; |
| 436 | 415 |
| 437 return a->hasTagName(h1Tag) | 416 return a->hasTagName(h1Tag) |
| 438 || a->hasTagName(h2Tag) | 417 || a->hasTagName(h2Tag) |
| 439 || a->hasTagName(h3Tag) | 418 || a->hasTagName(h3Tag) |
| 440 || a->hasTagName(h4Tag) | 419 || a->hasTagName(h4Tag) |
| 441 || a->hasTagName(h5Tag) | 420 || a->hasTagName(h5Tag) |
| 442 || a->hasTagName(h6Tag); | 421 || a->hasTagName(h6Tag); |
| 443 } | 422 } |
| 444 | 423 |
| 445 static bool haveSameTagName(Node* a, Node* b) | 424 static bool haveSameTagName(Node* a, Node* b) |
| 446 { | 425 { |
| 447 return a && b && a->isElementNode() && b->isElementNode() && toElement(a)->t
agName() == toElement(b)->tagName(); | 426 return a && b && a->isElementNode() && b->isElementNode() && toElement(a)->t
agName() == toElement(b)->tagName(); |
| 448 } | 427 } |
| 449 | 428 |
| 450 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V
isiblePosition& destination) | 429 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V
isiblePosition& destination) |
| 451 { | 430 { |
| 452 if (source.isNull() || destination.isNull()) | 431 if (source.isNull() || destination.isNull()) |
| 453 return false; | 432 return false; |
| 454 | 433 |
| 455 Node* sourceNode = source.deepEquivalent().deprecatedNode(); | 434 Node* sourceNode = source.deepEquivalent().deprecatedNode(); |
| 456 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); | 435 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); |
| 457 Node* sourceBlock = enclosingBlock(sourceNode); | 436 Node* sourceBlock = enclosingBlock(sourceNode); |
| 458 Node* destinationBlock = enclosingBlock(destinationNode); | 437 Node* destinationBlock = enclosingBlock(destinationNode); |
| 459 return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotation
Node) && | 438 return sourceBlock && !sourceBlock->hasTagName(blockquoteTag) && |
| 460 sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailBloc
kquote(sourceBlock)) && | |
| 461 enclosingListChild(sourceBlock) == enclosingListChild(destinationNode
) && | 439 enclosingListChild(sourceBlock) == enclosingListChild(destinationNode
) && |
| 462 enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(des
tination.deepEquivalent()) && | 440 enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(des
tination.deepEquivalent()) && |
| 463 (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destin
ationBlock)) && | 441 (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destin
ationBlock)) && |
| 464 // Don't merge to or from a position before or after a block because
it would | 442 // Don't merge to or from a position before or after a block because
it would |
| 465 // be a no-op and cause infinite recursion. | 443 // be a no-op and cause infinite recursion. |
| 466 !isBlock(sourceNode) && !isBlock(destinationNode); | 444 !isBlock(sourceNode) && !isBlock(destinationNode); |
| 467 } | 445 } |
| 468 | 446 |
| 469 // Style rules that match just inserted elements could change their appearance,
like | 447 // Style rules that match just inserted elements could change their appearance,
like |
| 470 // a div inserted into a document with div { display:inline; }. | 448 // a div inserted into a document with div { display:inline; }. |
| (...skipping 25 matching lines...) Expand all Loading... |
| 496 } else if (newInlineStyle->extractConflictingImplicitStyleOfAttr
ibutes(htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes, | 474 } else if (newInlineStyle->extractConflictingImplicitStyleOfAttr
ibutes(htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes, |
| 497 EditingStyle::DoNotExtractMatchingStyle)) { | 475 EditingStyle::DoNotExtractMatchingStyle)) { |
| 498 // e.g. <font size="3" style="font-size: 20px;"> is converte
d to <font style="font-size: 20px;"> | 476 // e.g. <font size="3" style="font-size: 20px;"> is converte
d to <font style="font-size: 20px;"> |
| 499 for (size_t i = 0; i < attributes.size(); i++) | 477 for (size_t i = 0; i < attributes.size(); i++) |
| 500 removeNodeAttribute(element, attributes[i]); | 478 removeNodeAttribute(element, attributes[i]); |
| 501 } | 479 } |
| 502 } | 480 } |
| 503 | 481 |
| 504 ContainerNode* context = element->parentNode(); | 482 ContainerNode* context = element->parentNode(); |
| 505 | 483 |
| 506 // If Mail wraps the fragment with a Paste as Quotation blockquote,
or if you're pasting into a quoted region, | |
| 507 // styles from blockquoteNode are allowed to override those from the
source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. | |
| 508 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context
: enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossE
ditingBoundary); | |
| 509 if (blockquoteNode) | |
| 510 newInlineStyle->removeStyleFromRulesAndContext(element, document
()->documentElement()); | |
| 511 | |
| 512 newInlineStyle->removeStyleFromRulesAndContext(element, context); | 484 newInlineStyle->removeStyleFromRulesAndContext(element, context); |
| 513 } | 485 } |
| 514 | 486 |
| 515 if (!inlineStyle || newInlineStyle->isEmpty()) { | 487 if (!inlineStyle || newInlineStyle->isEmpty()) { |
| 516 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element) || isEmptyFontT
ag(element, AllowNonEmptyStyleAttribute)) { | 488 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element) || isEmptyFontT
ag(element, AllowNonEmptyStyleAttribute)) { |
| 517 insertedNodes.willRemoveNodePreservingChildren(element); | 489 insertedNodes.willRemoveNodePreservingChildren(element); |
| 518 removeNodePreservingChildren(element); | 490 removeNodePreservingChildren(element); |
| 519 continue; | 491 continue; |
| 520 } | 492 } |
| 521 removeNodeAttribute(element, styleAttr); | 493 removeNodeAttribute(element, styleAttr); |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 713 next = NodeTraversal::next(node); | 685 next = NodeTraversal::next(node); |
| 714 } | 686 } |
| 715 } | 687 } |
| 716 | 688 |
| 717 // Remove style spans before insertion if they are unnecessary. It's faster bec
ause we'll | 689 // Remove style spans before insertion if they are unnecessary. It's faster bec
ause we'll |
| 718 // avoid doing a layout. | 690 // avoid doing a layout. |
| 719 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const
Position& insertionPos) | 691 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const
Position& insertionPos) |
| 720 { | 692 { |
| 721 Node* topNode = fragment.firstChild(); | 693 Node* topNode = fragment.firstChild(); |
| 722 | 694 |
| 723 // Handling the case where we are doing Paste as Quotation or pasting into q
uoted content is more complicated (see handleStyleSpans) | |
| 724 // and doesn't receive the optimization. | |
| 725 if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPosition
InOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary)) | |
| 726 return false; | |
| 727 | |
| 728 // Either there are no style spans in the fragment or a WebKit client has ad
ded content to the fragment | 695 // Either there are no style spans in the fragment or a WebKit client has ad
ded content to the fragment |
| 729 // before inserting it. Look for and handle style spans after insertion. | 696 // before inserting it. Look for and handle style spans after insertion. |
| 730 if (!isLegacyAppleStyleSpan(topNode)) | 697 if (!isLegacyAppleStyleSpan(topNode)) |
| 731 return false; | 698 return false; |
| 732 | 699 |
| 733 Node* wrappingStyleSpan = topNode; | 700 Node* wrappingStyleSpan = topNode; |
| 734 RefPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create(insertionPos
.parentAnchoredEquivalent()); | 701 RefPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create(insertionPos
.parentAnchoredEquivalent()); |
| 735 String styleText = styleAtInsertionPos->style()->asText(); | 702 String styleText = styleAtInsertionPos->style()->asText(); |
| 736 | 703 |
| 737 // FIXME: This string comparison is a naive way of comparing two styles. | 704 // FIXME: This string comparison is a naive way of comparing two styles. |
| (...skipping 27 matching lines...) Expand all Loading... |
| 765 } | 732 } |
| 766 | 733 |
| 767 // There might not be any style spans if we're pasting from another applicat
ion or if | 734 // There might not be any style spans if we're pasting from another applicat
ion or if |
| 768 // we are here because of a document.execCommand("InsertHTML", ...) call. | 735 // we are here because of a document.execCommand("InsertHTML", ...) call. |
| 769 if (!wrappingStyleSpan) | 736 if (!wrappingStyleSpan) |
| 770 return; | 737 return; |
| 771 | 738 |
| 772 RefPtr<EditingStyle> style = EditingStyle::create(wrappingStyleSpan->inlineS
tyle()); | 739 RefPtr<EditingStyle> style = EditingStyle::create(wrappingStyleSpan->inlineS
tyle()); |
| 773 ContainerNode* context = wrappingStyleSpan->parentNode(); | 740 ContainerNode* context = wrappingStyleSpan->parentNode(); |
| 774 | 741 |
| 775 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if yo
u're pasting into a quoted region, | |
| 776 // styles from blockquoteNode are allowed to override those from the source
document, see <rdar://problem/4930986> and <rdar://problem/5089327>. | |
| 777 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclo
singNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBo
undary); | |
| 778 if (blockquoteNode) | |
| 779 context = document()->documentElement(); | |
| 780 | |
| 781 // This operation requires that only editing styles to be removed from sourc
eDocumentStyle. | 742 // This operation requires that only editing styles to be removed from sourc
eDocumentStyle. |
| 782 style->prepareToApplyAt(firstPositionInNode(context)); | 743 style->prepareToApplyAt(firstPositionInNode(context)); |
| 783 | 744 |
| 784 // Remove block properties in the span's style. This prevents properties tha
t probably have no effect | 745 // Remove block properties in the span's style. This prevents properties tha
t probably have no effect |
| 785 // currently from affecting blocks later if the style is cloned for a new bl
ock element during a future | 746 // currently from affecting blocks later if the style is cloned for a new bl
ock element during a future |
| 786 // editing operation. | 747 // editing operation. |
| 787 // FIXME: They *can* have an effect currently if blocks beneath the style sp
an aren't individually marked | 748 // FIXME: They *can* have an effect currently if blocks beneath the style sp
an aren't individually marked |
| 788 // with block styles by the editing engine used to style them. WebKit doesn
't do this, but others might. | 749 // with block styles by the editing engine used to style them. WebKit doesn
't do this, but others might. |
| 789 style->removeBlockProperties(); | 750 style->removeBlockProperties(); |
| 790 | 751 |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 861 return false; | 822 return false; |
| 862 | 823 |
| 863 if (!node->isHTMLElement()) | 824 if (!node->isHTMLElement()) |
| 864 return false; | 825 return false; |
| 865 | 826 |
| 866 // We can skip over elements whose class attribute is | 827 // We can skip over elements whose class attribute is |
| 867 // one of our internal classes. | 828 // one of our internal classes. |
| 868 const HTMLElement* element = static_cast<const HTMLElement*>(node); | 829 const HTMLElement* element = static_cast<const HTMLElement*>(node); |
| 869 const AtomicString& classAttributeValue = element->getAttribute(classAttr); | 830 const AtomicString& classAttributeValue = element->getAttribute(classAttr); |
| 870 if (classAttributeValue == AppleTabSpanClass | 831 if (classAttributeValue == AppleTabSpanClass |
| 871 || classAttributeValue == AppleConvertedSpace | 832 || classAttributeValue == AppleConvertedSpace) |
| 872 || classAttributeValue == ApplePasteAsQuotation) | |
| 873 return true; | 833 return true; |
| 874 | 834 |
| 875 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(element); | 835 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(element); |
| 876 } | 836 } |
| 877 | 837 |
| 878 inline Node* nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(const Position& i
nsertionPos) | 838 inline Node* nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(const Position& i
nsertionPos) |
| 879 { | 839 { |
| 880 Node* containgBlock = enclosingBlock(insertionPos.containerNode()); | 840 Node* containgBlock = enclosingBlock(insertionPos.containerNode()); |
| 881 return highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle, Canno
tCrossEditingBoundary, containgBlock); | 841 return highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle, Canno
tCrossEditingBoundary, containgBlock); |
| 882 } | 842 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 908 | 868 |
| 909 VisiblePosition visibleStart = selection.visibleStart(); | 869 VisiblePosition visibleStart = selection.visibleStart(); |
| 910 VisiblePosition visibleEnd = selection.visibleEnd(); | 870 VisiblePosition visibleEnd = selection.visibleEnd(); |
| 911 | 871 |
| 912 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd); | 872 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd); |
| 913 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart); | 873 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart); |
| 914 | 874 |
| 915 Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNo
de()); | 875 Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNo
de()); |
| 916 | 876 |
| 917 Position insertionPos = selection.start(); | 877 Position insertionPos = selection.start(); |
| 918 bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailB
lockquote, CanCrossEditingBoundary); | |
| 919 bool selectionIsPlainText = !selection.isContentRichlyEditable(); | 878 bool selectionIsPlainText = !selection.isContentRichlyEditable(); |
| 920 Element* currentRoot = selection.rootEditableElement(); | 879 Element* currentRoot = selection.rootEditableElement(); |
| 921 | 880 |
| 922 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !
startIsInsideMailBlockquote) || | 881 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph) || |
| 923 startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainT
ext) | 882 startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainT
ext) |
| 924 m_preventNesting = false; | 883 m_preventNesting = false; |
| 925 | 884 |
| 926 if (selection.isRange()) { | 885 if (selection.isRange()) { |
| 927 // When the end of the selection being pasted into is at the end of a pa
ragraph, and that selection | 886 // When the end of the selection being pasted into is at the end of a pa
ragraph, and that selection |
| 928 // spans multiple blocks, not merging may leave an empty line. | 887 // spans multiple blocks, not merging may leave an empty line. |
| 929 // When the start of the selection being pasted into is at the start of
a block, not merging | 888 // When the start of the selection being pasted into is at the start of
a block, not merging |
| 930 // will leave hanging block(s). | 889 // will leave hanging block(s). |
| 931 // Merge blocks if the start of the selection was in a Mail blockquote,
since we handle | 890 bool mergeBlocksAfterDelete = isEndOfParagraph(visibleEnd) || isStartOfB
lock(visibleStart); |
| 932 // that case specially to prevent nesting. | |
| 933 bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfPara
graph(visibleEnd) || isStartOfBlock(visibleStart); | |
| 934 // FIXME: We should only expand to include fully selected special elemen
ts if we are copying a | 891 // FIXME: We should only expand to include fully selected special elemen
ts if we are copying a |
| 935 // selection and pasting it on top of itself. | 892 // selection and pasting it on top of itself. |
| 936 deleteSelection(false, mergeBlocksAfterDelete, true, false); | 893 deleteSelection(false, mergeBlocksAfterDelete, true, false); |
| 937 visibleStart = endingSelection().visibleStart(); | 894 visibleStart = endingSelection().visibleStart(); |
| 938 if (fragment.hasInterchangeNewlineAtStart()) { | 895 if (fragment.hasInterchangeNewlineAtStart()) { |
| 939 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta
rt)) { | 896 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta
rt)) { |
| 940 if (!isEndOfEditableOrNonEditableContent(visibleStart)) | 897 if (!isEndOfEditableOrNonEditableContent(visibleStart)) |
| 941 setEndingSelection(visibleStart.next()); | 898 setEndingSelection(visibleStart.next()); |
| 942 } else | 899 } else |
| 943 insertParagraphSeparator(); | 900 insertParagraphSeparator(); |
| 944 } | 901 } |
| 945 insertionPos = endingSelection().start(); | 902 insertionPos = endingSelection().start(); |
| 946 } else { | 903 } else { |
| 947 ASSERT(selection.isCaret()); | 904 ASSERT(selection.isCaret()); |
| 948 if (fragment.hasInterchangeNewlineAtStart()) { | 905 if (fragment.hasInterchangeNewlineAtStart()) { |
| 949 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary)
; | 906 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary)
; |
| 950 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta
rt) && next.isNotNull()) | 907 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta
rt) && next.isNotNull()) |
| 951 setEndingSelection(next); | 908 setEndingSelection(next); |
| 952 else { | 909 else { |
| 953 insertParagraphSeparator(); | 910 insertParagraphSeparator(); |
| 954 visibleStart = endingSelection().visibleStart(); | 911 visibleStart = endingSelection().visibleStart(); |
| 955 } | 912 } |
| 956 } | 913 } |
| 957 // We split the current paragraph in two to avoid nesting the blocks fro
m the fragment inside the current block. | 914 // We split the current paragraph in two to avoid nesting the blocks fro
m the fragment inside the current block. |
| 958 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <di
v>x^x</div>, where ^ is the caret. | 915 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <di
v>x^x</div>, where ^ is the caret. |
| 959 // As long as the div styles are the same, visually you'd expect: <div>
xbar</div><div>bar</div><div>bazx</div>, | 916 // As long as the div styles are the same, visually you'd expect: <div>
xbar</div><div>bar</div><div>bazx</div>, |
| 960 // not <div>xbar<div>bar</div><div>bazx</div></div>. | 917 // not <div>xbar<div>bar</div><div>bazx</div></div>. |
| 961 // Don't do this if the selection started in a Mail blockquote. | 918 if (m_preventNesting && !isEndOfParagraph(visibleStart) && !isStartOfPar
agraph(visibleStart)) { |
| 962 if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagrap
h(visibleStart) && !isStartOfParagraph(visibleStart)) { | |
| 963 insertParagraphSeparator(); | 919 insertParagraphSeparator(); |
| 964 setEndingSelection(endingSelection().visibleStart().previous()); | 920 setEndingSelection(endingSelection().visibleStart().previous()); |
| 965 } | 921 } |
| 966 insertionPos = endingSelection().start(); | 922 insertionPos = endingSelection().start(); |
| 967 } | 923 } |
| 968 | 924 |
| 969 // We don't want any of the pasted content to end up nested in a Mail blockq
uote, so first break | |
| 970 // out of any surrounding Mail blockquotes. Unless we're inserting in a tabl
e, in which case | |
| 971 // breaking the blockquote will prevent the content from actually being inse
rted in the table. | |
| 972 if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType
(insertionPos, &isTableStructureNode))) { | |
| 973 applyCommandToComposite(BreakBlockquoteCommand::create(document())); | |
| 974 // This will leave a br between the split. | |
| 975 Node* br = endingSelection().start().deprecatedNode(); | |
| 976 ASSERT(br->hasTagName(brTag)); | |
| 977 // Insert content between the two blockquotes, but remove the br (since
it was just a placeholder). | |
| 978 insertionPos = positionInParentBeforeNode(br); | |
| 979 removeNode(br); | |
| 980 } | |
| 981 | |
| 982 // Inserting content could cause whitespace to collapse, e.g. inserting <div
>foo</div> into hello^ world. | 925 // Inserting content could cause whitespace to collapse, e.g. inserting <div
>foo</div> into hello^ world. |
| 983 prepareWhitespaceAtPositionForSplit(insertionPos); | 926 prepareWhitespaceAtPositionForSplit(insertionPos); |
| 984 | 927 |
| 985 // If the downstream node has been removed there's no point in continuing. | 928 // If the downstream node has been removed there's no point in continuing. |
| 986 if (!insertionPos.downstream().deprecatedNode()) | 929 if (!insertionPos.downstream().deprecatedNode()) |
| 987 return; | 930 return; |
| 988 | 931 |
| 989 // NOTE: This would be an incorrect usage of downstream() if downstream() we
re changed to mean the last position after | 932 // NOTE: This would be an incorrect usage of downstream() if downstream() we
re changed to mean the last position after |
| 990 // 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 | 933 // 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 |
| 991 // away, there are positions after the br which map to the same visible posi
tion as [br, 0]). | 934 // away, there are positions after the br which map to the same visible posi
tion as [br, 0]). |
| 992 Node* endBR = insertionPos.downstream().deprecatedNode()->hasTagName(brTag)
? insertionPos.downstream().deprecatedNode() : 0; | 935 Node* endBR = insertionPos.downstream().deprecatedNode()->hasTagName(brTag)
? insertionPos.downstream().deprecatedNode() : 0; |
| 993 VisiblePosition originalVisPosBeforeEndBR; | 936 VisiblePosition originalVisPosBeforeEndBR; |
| 994 if (endBR) | 937 if (endBR) |
| 995 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR), D
OWNSTREAM).previous(); | 938 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR), D
OWNSTREAM).previous(); |
| 996 | 939 |
| 997 startBlock = enclosingBlock(insertionPos.deprecatedNode()); | 940 startBlock = enclosingBlock(insertionPos.deprecatedNode()); |
| 998 | 941 |
| 999 // Adjust insertionPos to prevent nesting. | 942 // Adjust insertionPos to prevent nesting. |
| 1000 // If the start was in a Mail blockquote, we will have already handled adjus
ting insertionPos above. | 943 if (m_preventNesting && startBlock && !isTableCell(startBlock)) { |
| 1001 if (m_preventNesting && startBlock && !isTableCell(startBlock) && !startIsIn
sideMailBlockquote) { | |
| 1002 ASSERT(startBlock != currentRoot); | 944 ASSERT(startBlock != currentRoot); |
| 1003 VisiblePosition visibleInsertionPos(insertionPos); | 945 VisiblePosition visibleInsertionPos(insertionPos); |
| 1004 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInserti
onPos) && fragment.hasInterchangeNewlineAtEnd())) | 946 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInserti
onPos) && fragment.hasInterchangeNewlineAtEnd())) |
| 1005 insertionPos = positionInParentAfterNode(startBlock); | 947 insertionPos = positionInParentAfterNode(startBlock); |
| 1006 else if (isStartOfBlock(visibleInsertionPos)) | 948 else if (isStartOfBlock(visibleInsertionPos)) |
| 1007 insertionPos = positionInParentBeforeNode(startBlock); | 949 insertionPos = positionInParentBeforeNode(startBlock); |
| 1008 } | 950 } |
| 1009 | 951 |
| 1010 // Paste at start or end of link goes outside of link. | 952 // Paste at start or end of link goes outside of link. |
| 1011 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos); | 953 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos); |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1138 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insert
edNodes.firstNodeInserted(), insertedNodes.pastLastLeaf())); | 1080 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insert
edNodes.firstNodeInserted(), insertedNodes.pastLastLeaf())); |
| 1139 | 1081 |
| 1140 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be
the last two lines of code that access insertedNodes. | 1082 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be
the last two lines of code that access insertedNodes. |
| 1141 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNo
deInserted()); | 1083 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNo
deInserted()); |
| 1142 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafIns
erted()); | 1084 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafIns
erted()); |
| 1143 | 1085 |
| 1144 // Determine whether or not we should merge the end of inserted content with
what's after it before we do | 1086 // Determine whether or not we should merge the end of inserted content with
what's after it before we do |
| 1145 // the start merge so that the start merge doesn't effect our decision. | 1087 // the start merge so that the start merge doesn't effect our decision. |
| 1146 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph); | 1088 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph); |
| 1147 | 1089 |
| 1148 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasIntercha
ngeNewlineAtStart(), startIsInsideMailBlockquote)) { | 1090 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasIntercha
ngeNewlineAtStart())) { |
| 1149 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedConten
t(); | 1091 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedConten
t(); |
| 1150 VisiblePosition destination = startOfParagraphToMove.previous(); | 1092 VisiblePosition destination = startOfParagraphToMove.previous(); |
| 1151 // We need to handle the case where we need to merge the end | 1093 // We need to handle the case where we need to merge the end |
| 1152 // but our destination node is inside an inline that is the last in the
block. | 1094 // but our destination node is inside an inline that is the last in the
block. |
| 1153 // We insert a placeholder before the newly inserted content to avoid be
ing merged into the inline. | 1095 // We insert a placeholder before the newly inserted content to avoid be
ing merged into the inline. |
| 1154 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); | 1096 Node* destinationNode = destination.deepEquivalent().deprecatedNode(); |
| 1155 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNo
de) && enclosingInline(destinationNode)->nextSibling()) | 1097 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNo
de) && enclosingInline(destinationNode)->nextSibling()) |
| 1156 insertNodeBefore(createBreakElement(document()), refNode.get()); | 1098 insertNodeBefore(createBreakElement(document()), refNode.get()); |
| 1157 | 1099 |
| 1158 // Merging the the first paragraph of inserted content with the content
that came | 1100 // Merging the the first paragraph of inserted content with the content
that came |
| (...skipping 28 matching lines...) Expand all Loading... |
| 1187 if (!isStartOfParagraph(endOfInsertedContent)) { | 1129 if (!isStartOfParagraph(endOfInsertedContent)) { |
| 1188 setEndingSelection(endOfInsertedContent); | 1130 setEndingSelection(endOfInsertedContent); |
| 1189 Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEq
uivalent().deprecatedNode()); | 1131 Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEq
uivalent().deprecatedNode()); |
| 1190 if (isListItem(enclosingNode)) { | 1132 if (isListItem(enclosingNode)) { |
| 1191 RefPtr<Node> newListItem = createListItemElement(document())
; | 1133 RefPtr<Node> newListItem = createListItemElement(document())
; |
| 1192 insertNodeAfter(newListItem, enclosingNode); | 1134 insertNodeAfter(newListItem, enclosingNode); |
| 1193 setEndingSelection(VisiblePosition(firstPositionInNode(newLi
stItem.get()))); | 1135 setEndingSelection(VisiblePosition(firstPositionInNode(newLi
stItem.get()))); |
| 1194 } else { | 1136 } else { |
| 1195 // Use a default paragraph element (a plain div) for the emp
ty paragraph, using the last paragraph | 1137 // Use a default paragraph element (a plain div) for the emp
ty paragraph, using the last paragraph |
| 1196 // block's style seems to annoy users. | 1138 // block's style seems to annoy users. |
| 1197 insertParagraphSeparator(true, !startIsInsideMailBlockquote
&& highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(), | 1139 insertParagraphSeparator(true); |
| 1198 isMailBlockquote, CannotCrossEditingBoundary, insertedNo
des.firstNodeInserted()->parentNode())); | |
| 1199 } | 1140 } |
| 1200 | 1141 |
| 1201 // Select up to the paragraph separator that was added. | 1142 // Select up to the paragraph separator that was added. |
| 1202 lastPositionToSelect = endingSelection().visibleStart().deepEqui
valent(); | 1143 lastPositionToSelect = endingSelection().visibleStart().deepEqui
valent(); |
| 1203 updateNodesInserted(lastPositionToSelect.deprecatedNode()); | 1144 updateNodesInserted(lastPositionToSelect.deprecatedNode()); |
| 1204 } | 1145 } |
| 1205 } else { | 1146 } else { |
| 1206 // Select up to the beginning of the next paragraph. | 1147 // Select up to the beginning of the next paragraph. |
| 1207 lastPositionToSelect = next.deepEquivalent().downstream(); | 1148 lastPositionToSelect = next.deepEquivalent().downstream(); |
| 1208 } | 1149 } |
| 1209 | 1150 |
| 1210 } else | 1151 } else |
| 1211 mergeEndIfNeeded(); | 1152 mergeEndIfNeeded(); |
| 1212 | 1153 |
| 1213 if (Node* mailBlockquote = enclosingNodeOfType(positionAtStartOfInsertedCont
ent().deepEquivalent(), isMailPasteAsQuotationNode)) | |
| 1214 removeNodeAttribute(toElement(mailBlockquote), classAttr); | |
| 1215 | |
| 1216 if (shouldPerformSmartReplace()) | 1154 if (shouldPerformSmartReplace()) |
| 1217 addSpacesForSmartReplace(); | 1155 addSpacesForSmartReplace(); |
| 1218 | 1156 |
| 1219 // If we are dealing with a fragment created from plain text | 1157 // If we are dealing with a fragment created from plain text |
| 1220 // no style matching is necessary. | 1158 // no style matching is necessary. |
| 1221 if (plainTextFragment) | 1159 if (plainTextFragment) |
| 1222 m_matchStyle = false; | 1160 m_matchStyle = false; |
| 1223 | 1161 |
| 1224 completeHTMLReplacement(lastPositionToSelect); | 1162 completeHTMLReplacement(lastPositionToSelect); |
| 1225 } | 1163 } |
| (...skipping 263 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1489 removeNodeAndPruneAncestors(nodeAfterInsertionPos.get()); | 1427 removeNodeAndPruneAncestors(nodeAfterInsertionPos.get()); |
| 1490 | 1428 |
| 1491 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, en
d); | 1429 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, en
d); |
| 1492 | 1430 |
| 1493 setEndingSelection(selectionAfterReplace); | 1431 setEndingSelection(selectionAfterReplace); |
| 1494 | 1432 |
| 1495 return true; | 1433 return true; |
| 1496 } | 1434 } |
| 1497 | 1435 |
| 1498 } // namespace WebCore | 1436 } // namespace WebCore |
| OLD | NEW |