diff options
Diffstat (limited to 'Source/WebCore/editing/ReplaceSelectionCommand.cpp')
-rw-r--r-- | Source/WebCore/editing/ReplaceSelectionCommand.cpp | 80 |
1 files changed, 66 insertions, 14 deletions
diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp index b0a2d68..94531a6 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp @@ -43,6 +43,7 @@ #include "HTMLInputElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" +#include "NodeList.h" #include "SelectionController.h" #include "SmartReplace.h" #include "TextIterator.h" @@ -110,7 +111,7 @@ static bool isInterchangeConvertedSpaceSpan(const Node *node) static Position positionAvoidingPrecedingNodes(Position pos) { // If we're already on a break, it's probably a placeholder and we shouldn't change our position. - if (pos.deprecatedNode()->hasTagName(brTag)) + if (editingIgnoresContent(pos.deprecatedNode())) return pos; // We also stop when changing block flow elements because even though the visual position is the @@ -147,7 +148,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) && // FIXME: Remove these checks once textareas and textfields actually register an event handler. !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) && - editableRoot->isContentRichlyEditable()) { + editableRoot->rendererIsRichlyEditable()) { removeInterchangeNodes(m_fragment.get()); return; } @@ -162,7 +163,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f ExceptionCode ec = 0; editableRoot->dispatchEvent(evt, ec); ASSERT(ec == 0); - if (text != evt->text() || !editableRoot->isContentRichlyEditable()) { + if (text != evt->text() || !editableRoot->rendererIsRichlyEditable()) { restoreTestRenderingNodesToFragment(holder.get()); removeNode(holder); @@ -357,7 +358,7 @@ static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisibleP { Position existing = endOfExistingContent.deepEquivalent(); Position inserted = endOfInsertedContent.deepEquivalent(); - bool isInsideMailBlockquote = nearestMailBlockquote(inserted.deprecatedNode()); + bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote, CanCrossEditingBoundary); return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted)); } @@ -367,7 +368,7 @@ bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara return false; VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); - VisiblePosition prev = startOfInsertedContent.previous(true); + VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBoundary); if (prev.isNull()) return false; @@ -389,7 +390,7 @@ bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) { VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); - VisiblePosition next = endOfInsertedContent.next(true); + VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); if (next.isNull()) return false; @@ -536,10 +537,10 @@ VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() { Node* lastNode = m_lastLeafInserted.get(); // FIXME: Why is this hack here? What's special about <select> tags? - Node* enclosingSelect = enclosingNodeWithTag(firstDeepEditingPositionForNode(lastNode), selectTag); + Node* enclosingSelect = enclosingNodeWithTag(firstPositionInOrBeforeNode(lastNode), selectTag); if (enclosingSelect) lastNode = enclosingSelect; - return lastDeepEditingPositionForNode(lastNode); + return lastPositionInOrAfterNode(lastNode); } VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() @@ -556,7 +557,7 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans) // and doesn't receive the optimization. - if (isMailPasteAsQuotationNode(topNode) || nearestMailBlockquote(topNode)) + if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary)) return false; // Either there are no style spans in the fragment or a WebKit client has added content to the fragment @@ -623,7 +624,7 @@ void ReplaceSelectionCommand::handleStyleSpans() // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. - Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : nearestMailBlockquote(context); + Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary); if (blockquoteNode) { sourceDocumentStyle->removeStyleConflictingWithStyleOfNode(blockquoteNode); context = blockquoteNode->parentNode(); @@ -776,6 +777,34 @@ static Node* enclosingInline(Node* node) return node; } +static bool isInlineNodeWithStyle(const Node* node) +{ + // We don't want to skip over any block elements. + if (!node->renderer() || !node->renderer()->isInline()) + return false; + + if (!node->isHTMLElement()) + return false; + + // We can skip over elements whose class attribute is + // one of our internal classes. + const HTMLElement* element = static_cast<const HTMLElement*>(node); + AtomicString classAttributeValue = element->getAttribute(classAttr); + if (classAttributeValue == AppleStyleSpanClass + || classAttributeValue == AppleTabSpanClass + || classAttributeValue == AppleConvertedSpace + || classAttributeValue == ApplePasteAsQuotation) + return true; + + // We can skip inline elements that don't have attributes or whose only + // attribute is the style attribute. + const NamedNodeMap* attributeMap = element->attributeMap(); + if (!attributeMap || attributeMap->isEmpty() || (attributeMap->length() == 1 && element->hasAttribute(styleAttr))) + return true; + + return false; +} + void ReplaceSelectionCommand::doApply() { VisibleSelection selection = endingSelection(); @@ -811,8 +840,8 @@ void ReplaceSelectionCommand::doApply() Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode()); Position insertionPos = selection.start(); - bool startIsInsideMailBlockquote = nearestMailBlockquote(insertionPos.deprecatedNode()); - + bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary); + if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) || startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText) m_preventNesting = false; @@ -840,7 +869,7 @@ void ReplaceSelectionCommand::doApply() } else { ASSERT(selection.isCaret()); if (fragment.hasInterchangeNewlineAtStart()) { - VisiblePosition next = visibleStart.next(true); + VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary); if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull()) setEndingSelection(next); else @@ -917,6 +946,29 @@ void ReplaceSelectionCommand::doApply() // outside of preceding tags. insertionPos = positionAvoidingPrecedingNodes(insertionPos); + // If we are not trying to match the destination style we prefer a position + // that is outside inline elements that provide style. + // This way we can produce a less verbose markup. + // We can skip this optimization for fragments not wrapped in one of + // our style spans and for positions inside list items + // since insertAsListItems already does the right thing. + if (!m_matchStyle && !enclosingList(insertionPos.anchorNode()) && isStyleSpan(fragment.firstChild())) { + Node* parentNode = insertionPos.anchorNode()->parentNode(); + while (parentNode && parentNode->renderer() && isInlineNodeWithStyle(parentNode)) { + // If we are in the middle of a text node, we need to split it before we can + // move the insertion position. + if (insertionPos.anchorNode()->isTextNode() && insertionPos.anchorType() == Position::PositionIsOffsetInAnchor && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) + splitTextNodeContainingElement(static_cast<Text*>(insertionPos.anchorNode()), insertionPos.offsetInContainerNode()); + + // If the style element has more than one child, we need to split it. + if (parentNode->firstChild()->nextSibling()) + splitElement(static_cast<Element*>(parentNode), insertionPos.computeNodeAfterPosition()); + + insertionPos = positionInParentBeforeNode(parentNode); + parentNode = parentNode->parentNode(); + } + } + // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try // again here if they've been removed. @@ -1036,7 +1088,7 @@ void ReplaceSelectionCommand::doApply() startOfInsertedContent = positionAtStartOfInsertedContent(); if (interchangeNewlineAtEnd) { - VisiblePosition next = endOfInsertedContent.next(true); + VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) { if (!isStartOfParagraph(endOfInsertedContent)) { |