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 654 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
665 if (editingState->isAborted()) | 665 if (editingState->isAborted()) |
666 return; | 666 return; |
667 continue; | 667 continue; |
668 } | 668 } |
669 | 669 |
670 if (element->parentNode() && | 670 if (element->parentNode() && |
671 hasRichlyEditableStyle(*element->parentNode()) && | 671 hasRichlyEditableStyle(*element->parentNode()) && |
672 hasRichlyEditableStyle(*element)) { | 672 hasRichlyEditableStyle(*element)) { |
673 removeElementAttribute(element, contenteditableAttr); | 673 removeElementAttribute(element, contenteditableAttr); |
674 } | 674 } |
675 | |
676 // WebKit used to not add display: inline and float: none on copy. | |
677 // Keep this code around for backward compatibility | |
678 if (isLegacyAppleHTMLSpanElement(element)) { | |
679 if (!element->hasChildren()) { | |
680 insertedNodes.willRemoveNodePreservingChildren(*element); | |
681 removeNodePreservingChildren(element, editingState); | |
682 if (editingState->isAborted()) | |
683 return; | |
684 continue; | |
685 } | |
686 // There are other styles that style rules can give to style spans, but | |
687 // these are the two important ones because they'll prevent inserted | |
688 // content from appearing in the right paragraph. | |
689 // FIXME: Hyatt is concerned that selectively using display:inline will | |
690 // give inconsistent results. We already know one issue because td | |
691 // elements ignore their display property in quirks mode (which Mail.app | |
692 // is always in). We should look for an alternative. | |
693 | |
694 // Mutate using the CSSOM wrapper so we get the same event behavior as a | |
695 // script. | |
696 if (isEnclosingBlock(element)) { | |
697 element->style()->setPropertyInternal(CSSPropertyDisplay, String(), | |
698 "inline", false, | |
699 IGNORE_EXCEPTION_FOR_TESTING); | |
700 } | |
701 if (element->layoutObject() && | |
702 element->layoutObject()->style()->isFloating()) { | |
703 element->style()->setPropertyInternal(CSSPropertyFloat, String(), | |
704 "none", false, | |
705 IGNORE_EXCEPTION_FOR_TESTING); | |
706 } | |
707 } | |
708 } | 675 } |
709 } | 676 } |
710 | 677 |
711 static bool isProhibitedParagraphChild(const AtomicString& name) { | 678 static bool isProhibitedParagraphChild(const AtomicString& name) { |
712 // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibite
d-paragraph-child | 679 // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibite
d-paragraph-child |
713 DEFINE_STATIC_LOCAL(HashSet<AtomicString>, elements, | 680 DEFINE_STATIC_LOCAL(HashSet<AtomicString>, elements, |
714 ({ | 681 ({ |
715 addressTag.localName(), articleTag.localName(), | 682 addressTag.localName(), articleTag.localName(), |
716 asideTag.localName(), blockquoteTag.localName(), | 683 asideTag.localName(), blockquoteTag.localName(), |
717 captionTag.localName(), centerTag.localName(), | 684 captionTag.localName(), centerTag.localName(), |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
891 | 858 |
892 const HTMLElement& element = toHTMLElement(*node); | 859 const HTMLElement& element = toHTMLElement(*node); |
893 return isListItem(node) || isTableCell(node) || element.hasTagName(preTag) || | 860 return isListItem(node) || isTableCell(node) || element.hasTagName(preTag) || |
894 element.hasTagName(h1Tag) || element.hasTagName(h2Tag) || | 861 element.hasTagName(h1Tag) || element.hasTagName(h2Tag) || |
895 element.hasTagName(h3Tag) || element.hasTagName(h4Tag) || | 862 element.hasTagName(h3Tag) || element.hasTagName(h4Tag) || |
896 element.hasTagName(h5Tag) || element.hasTagName(h6Tag); | 863 element.hasTagName(h5Tag) || element.hasTagName(h6Tag); |
897 } | 864 } |
898 | 865 |
899 // Remove style spans before insertion if they are unnecessary. It's faster | 866 // Remove style spans before insertion if they are unnecessary. It's faster |
900 // because we'll avoid doing a layout. | 867 // because we'll avoid doing a layout. |
901 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, | 868 static void handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, |
902 const Position& insertionPos) { | 869 const Position& insertionPos) { |
903 Node* topNode = fragment.firstChild(); | 870 Node* topNode = fragment.firstChild(); |
904 if (!isHTMLSpanElement(topNode)) | 871 if (!isHTMLSpanElement(topNode)) |
905 return false; | 872 return; |
906 | 873 |
907 // Handling the case where we are doing Paste as Quotation or pasting into | 874 // Handling the case where we are doing Paste as Quotation or pasting into |
908 // quoted content is more complicated (see handleStyleSpans) and doesn't | 875 // quoted content is more complicated (see handleStyleSpans) and doesn't |
909 // receive the optimization. | 876 // receive the optimization. |
910 if (isMailPasteAsQuotationHTMLBlockQuoteElement(topNode) || | 877 if (isMailPasteAsQuotationHTMLBlockQuoteElement(topNode) || |
911 enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), | 878 enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), |
912 isMailHTMLBlockquoteElement, CanCrossEditingBoundary)) | 879 isMailHTMLBlockquoteElement, CanCrossEditingBoundary)) |
913 return false; | 880 return; |
914 | 881 |
915 // Remove style spans to follow the styles of parent block element when | 882 // Remove style spans to follow the styles of parent block element when |
916 // |fragment| becomes a part of it. See bugs http://crbug.com/226941 and | 883 // |fragment| becomes a part of it. See bugs http://crbug.com/226941 and |
917 // http://crbug.com/335955. | 884 // http://crbug.com/335955. |
918 HTMLSpanElement* wrappingStyleSpan = toHTMLSpanElement(topNode); | 885 HTMLSpanElement* wrappingStyleSpan = toHTMLSpanElement(topNode); |
919 const Node* node = insertionPos.anchorNode(); | 886 const Node* node = insertionPos.anchorNode(); |
920 // |node| can be an inline element like <br> under <li> | 887 // |node| can be an inline element like <br> under <li> |
921 // e.g.) editing/execCommand/switch-list-type.html | 888 // e.g.) editing/execCommand/switch-list-type.html |
922 // editing/deleting/backspace-merge-into-block.html | 889 // editing/deleting/backspace-merge-into-block.html |
923 if (isInline(node)) { | 890 if (isInline(node)) { |
924 node = enclosingBlock(insertionPos.anchorNode()); | 891 node = enclosingBlock(insertionPos.anchorNode()); |
925 if (!node) | 892 if (!node) |
926 return false; | 893 return; |
927 } | 894 } |
928 | 895 |
929 if (followBlockElementStyle(node)) { | 896 if (followBlockElementStyle(node)) { |
930 fragment.removeNodePreservingChildren(wrappingStyleSpan); | 897 fragment.removeNodePreservingChildren(wrappingStyleSpan); |
931 return true; | 898 return; |
932 } | 899 } |
933 | 900 |
934 // Either there are no style spans in the fragment or a WebKit client has | |
935 // added content to the fragment before inserting it. Look for and handle | |
936 // style spans after insertion. | |
937 if (!isLegacyAppleHTMLSpanElement(topNode)) | |
938 return false; | |
939 | |
940 EditingStyle* styleAtInsertionPos = | 901 EditingStyle* styleAtInsertionPos = |
941 EditingStyle::create(insertionPos.parentAnchoredEquivalent()); | 902 EditingStyle::create(insertionPos.parentAnchoredEquivalent()); |
942 String styleText = styleAtInsertionPos->style()->asText(); | 903 String styleText = styleAtInsertionPos->style()->asText(); |
943 | 904 |
944 // FIXME: This string comparison is a naive way of comparing two styles. | 905 // FIXME: This string comparison is a naive way of comparing two styles. |
945 // We should be taking the diff and check that the diff is empty. | 906 // We should be taking the diff and check that the diff is empty. |
946 if (styleText != wrappingStyleSpan->getAttribute(styleAttr)) | 907 if (styleText != wrappingStyleSpan->getAttribute(styleAttr)) |
947 return false; | 908 return; |
948 | 909 |
949 fragment.removeNodePreservingChildren(wrappingStyleSpan); | 910 fragment.removeNodePreservingChildren(wrappingStyleSpan); |
950 return true; | |
951 } | |
952 | |
953 // At copy time, WebKit wraps copied content in a span that contains the source | |
954 // document's default styles. If the copied Range inherits any other styles | |
955 // from its ancestors, we put those styles on a second span. This function | |
956 // removes redundant styles from those spans, and removes the | |
957 // spans if all their styles are redundant. | |
958 // We should remove the Apple-style-span class when we're done, see | |
959 // <rdar://problem/5685600>. | |
960 // We should remove styles from spans that are overridden by all of their | |
961 // children, either here or at copy time. | |
962 void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes, | |
963 EditingState* editingState) { | |
964 if (!insertedNodes.firstNodeInserted()) | |
965 return; | |
966 | |
967 HTMLSpanElement* wrappingStyleSpan = nullptr; | |
968 // The style span that contains the source document's default style should be | |
969 // at the top of the fragment, but Mail sometimes adds a wrapper (for Paste As | |
970 // Quotation), so search for the top level style span instead of assuming it's | |
971 // at the top. | |
972 | |
973 for (Node& node : | |
974 NodeTraversal::startsAt(*insertedNodes.firstNodeInserted())) { | |
975 if (isLegacyAppleHTMLSpanElement(&node)) { | |
976 wrappingStyleSpan = toHTMLSpanElement(&node); | |
977 break; | |
978 } | |
979 } | |
980 | |
981 // There might not be any style spans if we're pasting from another | |
982 // application or if we are here because of a | |
983 // document.execCommand("InsertHTML", ...) call. | |
984 if (!wrappingStyleSpan) | |
985 return; | |
986 | |
987 EditingStyle* style = EditingStyle::create(wrappingStyleSpan->inlineStyle()); | |
988 ContainerNode* context = wrappingStyleSpan->parentNode(); | |
989 | |
990 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if | |
991 // you're pasting into a quoted region, styles from blockquoteElement are | |
992 // allowed to override those from the source document, see | |
993 // <rdar://problem/4930986> and <rdar://problem/5089327>. | |
994 HTMLQuoteElement* blockquoteElement = | |
995 isMailPasteAsQuotationHTMLBlockQuoteElement(context) | |
996 ? toHTMLQuoteElement(context) | |
997 : toHTMLQuoteElement(enclosingNodeOfType( | |
998 Position::firstPositionInNode(context), | |
999 isMailHTMLBlockquoteElement, CanCrossEditingBoundary)); | |
1000 if (blockquoteElement) | |
1001 context = document().documentElement(); | |
1002 | |
1003 // This operation requires that only editing styles to be removed from | |
1004 // sourceDocumentStyle. | |
1005 style->prepareToApplyAt(Position::firstPositionInNode(context)); | |
1006 | |
1007 // Remove block properties in the span's style. This prevents properties that | |
1008 // probably have no effect currently from affecting blocks later if the style | |
1009 // is cloned for a new block element during a future | |
1010 // editing operation. | |
1011 // FIXME: They *can* have an effect currently if blocks beneath the style span | |
1012 // aren't individually marked with block styles by the editing engine used to | |
1013 // style them. WebKit doesn't do this, but others might. | |
1014 style->removeBlockProperties(); | |
1015 | |
1016 if (style->isEmpty() || !wrappingStyleSpan->hasChildren()) { | |
1017 insertedNodes.willRemoveNodePreservingChildren(*wrappingStyleSpan); | |
1018 removeNodePreservingChildren(wrappingStyleSpan, editingState); | |
1019 } else { | |
1020 setNodeAttribute(wrappingStyleSpan, styleAttr, | |
1021 AtomicString(style->style()->asText())); | |
1022 } | |
1023 } | 911 } |
1024 | 912 |
1025 void ReplaceSelectionCommand::mergeEndIfNeeded(EditingState* editingState) { | 913 void ReplaceSelectionCommand::mergeEndIfNeeded(EditingState* editingState) { |
1026 if (!m_shouldMergeEnd) | 914 if (!m_shouldMergeEnd) |
1027 return; | 915 return; |
1028 | 916 |
1029 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); | 917 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); |
1030 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); | 918 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); |
1031 | 919 |
1032 // Bail to avoid infinite recursion. | 920 // Bail to avoid infinite recursion. |
(...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1366 | 1254 |
1367 // We don't want the destination to end up inside nodes that weren't selected. | 1255 // We don't want the destination to end up inside nodes that weren't selected. |
1368 // To avoid that, we move the position forward without changing the visible | 1256 // To avoid that, we move the position forward without changing the visible |
1369 // position so we're still at the same visible location, but outside of | 1257 // position so we're still at the same visible location, but outside of |
1370 // preceding tags. | 1258 // preceding tags. |
1371 insertionPos = positionAvoidingPrecedingNodes(insertionPos); | 1259 insertionPos = positionAvoidingPrecedingNodes(insertionPos); |
1372 | 1260 |
1373 // Paste into run of tabs splits the tab span. | 1261 // Paste into run of tabs splits the tab span. |
1374 insertionPos = positionOutsideTabSpan(insertionPos); | 1262 insertionPos = positionOutsideTabSpan(insertionPos); |
1375 | 1263 |
1376 bool handledStyleSpans = | 1264 handleStyleSpansBeforeInsertion(fragment, insertionPos); |
1377 handleStyleSpansBeforeInsertion(fragment, insertionPos); | |
1378 | 1265 |
1379 // We're finished if there is nothing to add. | 1266 // We're finished if there is nothing to add. |
1380 if (fragment.isEmpty() || !fragment.firstChild()) | 1267 if (fragment.isEmpty() || !fragment.firstChild()) |
1381 return; | 1268 return; |
1382 | 1269 |
1383 // If we are not trying to match the destination style we prefer a position | 1270 // If we are not trying to match the destination style we prefer a position |
1384 // that is outside inline elements that provide style. | 1271 // that is outside inline elements that provide style. |
1385 // This way we can produce a less verbose markup. | 1272 // This way we can produce a less verbose markup. |
1386 // We can skip this optimization for fragments not wrapped in one of | 1273 // We can skip this optimization for fragments not wrapped in one of |
1387 // our style spans and for positions inside list items | 1274 // our style spans and for positions inside list items |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1429 | 1316 |
1430 InsertedNodes insertedNodes; | 1317 InsertedNodes insertedNodes; |
1431 insertedNodes.setRefNode(fragment.firstChild()); | 1318 insertedNodes.setRefNode(fragment.firstChild()); |
1432 DCHECK(insertedNodes.refNode()); | 1319 DCHECK(insertedNodes.refNode()); |
1433 Node* node = insertedNodes.refNode()->nextSibling(); | 1320 Node* node = insertedNodes.refNode()->nextSibling(); |
1434 | 1321 |
1435 fragment.removeNode(insertedNodes.refNode()); | 1322 fragment.removeNode(insertedNodes.refNode()); |
1436 | 1323 |
1437 Element* blockStart = enclosingBlock(insertionPos.anchorNode()); | 1324 Element* blockStart = enclosingBlock(insertionPos.anchorNode()); |
1438 if ((isHTMLListElement(insertedNodes.refNode()) || | 1325 if ((isHTMLListElement(insertedNodes.refNode()) || |
1439 (isLegacyAppleHTMLSpanElement(insertedNodes.refNode()) && | 1326 (isHTMLListElement(insertedNodes.refNode()->firstChild()))) && |
1440 isHTMLListElement(insertedNodes.refNode()->firstChild()))) && | |
1441 blockStart && blockStart->layoutObject()->isListItem() && | 1327 blockStart && blockStart->layoutObject()->isListItem() && |
1442 hasEditableStyle(*blockStart->parentNode())) { | 1328 hasEditableStyle(*blockStart->parentNode())) { |
1443 insertedNodes.setRefNode( | 1329 insertedNodes.setRefNode( |
1444 insertAsListItems(toHTMLElement(insertedNodes.refNode()), blockStart, | 1330 insertAsListItems(toHTMLElement(insertedNodes.refNode()), blockStart, |
1445 insertionPos, insertedNodes, editingState)); | 1331 insertionPos, insertedNodes, editingState)); |
1446 if (editingState->isAborted()) | 1332 if (editingState->isAborted()) |
1447 return; | 1333 return; |
1448 } else { | 1334 } else { |
1449 insertNodeAt(insertedNodes.refNode(), insertionPos, editingState); | 1335 insertNodeAt(insertedNodes.refNode(), insertionPos, editingState); |
1450 if (editingState->isAborted()) | 1336 if (editingState->isAborted()) |
(...skipping 21 matching lines...) Expand all Loading... |
1472 | 1358 |
1473 insertedNodes.setRefNode(node); | 1359 insertedNodes.setRefNode(node); |
1474 if (node && plainTextFragment) | 1360 if (node && plainTextFragment) |
1475 plainTextFragment = isPlainTextMarkup(node); | 1361 plainTextFragment = isPlainTextMarkup(node); |
1476 node = next; | 1362 node = next; |
1477 } | 1363 } |
1478 | 1364 |
1479 if (isRichlyEditablePosition(insertionPos)) | 1365 if (isRichlyEditablePosition(insertionPos)) |
1480 removeUnrenderedTextNodesAtEnds(insertedNodes); | 1366 removeUnrenderedTextNodesAtEnds(insertedNodes); |
1481 | 1367 |
1482 if (!handledStyleSpans) { | |
1483 handleStyleSpans(insertedNodes, editingState); | |
1484 if (editingState->isAborted()) | |
1485 return; | |
1486 } | |
1487 | |
1488 document().updateStyleAndLayoutIgnorePendingStylesheets(); | 1368 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
1489 | 1369 |
1490 // Mutation events (bug 20161) may have already removed the inserted content | 1370 // Mutation events (bug 20161) may have already removed the inserted content |
1491 if (!insertedNodes.firstNodeInserted() || | 1371 if (!insertedNodes.firstNodeInserted() || |
1492 !insertedNodes.firstNodeInserted()->isConnected()) | 1372 !insertedNodes.firstNodeInserted()->isConnected()) |
1493 return; | 1373 return; |
1494 | 1374 |
1495 // Scripts specified in javascript protocol may remove | 1375 // Scripts specified in javascript protocol may remove |
1496 // |enclosingBlockOfInsertionPos| during insertion, e.g. <iframe | 1376 // |enclosingBlockOfInsertionPos| during insertion, e.g. <iframe |
1497 // src="javascript:..."> | 1377 // src="javascript:..."> |
(...skipping 642 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2140 visitor->trace(m_startOfInsertedContent); | 2020 visitor->trace(m_startOfInsertedContent); |
2141 visitor->trace(m_endOfInsertedContent); | 2021 visitor->trace(m_endOfInsertedContent); |
2142 visitor->trace(m_insertionStyle); | 2022 visitor->trace(m_insertionStyle); |
2143 visitor->trace(m_documentFragment); | 2023 visitor->trace(m_documentFragment); |
2144 visitor->trace(m_startOfInsertedRange); | 2024 visitor->trace(m_startOfInsertedRange); |
2145 visitor->trace(m_endOfInsertedRange); | 2025 visitor->trace(m_endOfInsertedRange); |
2146 CompositeEditCommand::trace(visitor); | 2026 CompositeEditCommand::trace(visitor); |
2147 } | 2027 } |
2148 | 2028 |
2149 } // namespace blink | 2029 } // namespace blink |
OLD | NEW |