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 |