summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/editing/ReplaceSelectionCommand.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/editing/ReplaceSelectionCommand.cpp')
-rw-r--r--Source/WebCore/editing/ReplaceSelectionCommand.cpp80
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)) {