summaryrefslogtreecommitdiffstats
path: root/WebCore/editing/ReplaceSelectionCommand.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing/ReplaceSelectionCommand.cpp')
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp84
1 files changed, 59 insertions, 25 deletions
diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp
index 15fc736..09ad985 100644
--- a/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -61,7 +61,7 @@ enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
class ReplacementFragment : Noncopyable {
public:
- ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const Selection&);
+ ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const VisibleSelection&);
Node* firstChild() const;
Node* lastChild() const;
@@ -103,7 +103,7 @@ static bool isInterchangeConvertedSpaceSpan(const Node *node)
static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
}
-ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
+ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const VisibleSelection& selection)
: m_document(document),
m_fragment(fragment),
m_matchStyle(matchStyle),
@@ -126,8 +126,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
if (!editableRoot->inlineEventListenerForType(eventNames().webkitBeforeTextInsertedEvent) &&
// FIXME: Remove these checks once textareas and textfields actually register an event handler.
- !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextField()) &&
- !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextArea()) &&
+ !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) &&
editableRoot->isContentRichlyEditable()) {
removeInterchangeNodes(m_fragment.get());
return;
@@ -136,7 +135,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
Node* styleNode = selection.base().node();
RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
- RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange();
+ RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange();
String text = plainText(range.get());
// Give the root a chance to change the text.
RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
@@ -147,7 +146,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
restoreTestRenderingNodesToFragment(holder.get());
removeNode(holder);
- m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
+ m_fragment = createFragmentFromText(selection.toNormalizedRange().get(), evt->text());
if (!m_fragment->firstChild())
return;
holder = insertFragmentForTestRendering(styleNode);
@@ -406,6 +405,23 @@ void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
m_firstNodeInserted = m_lastLeafInserted && m_lastLeafInserted->inDocument() ? afterFirst : 0;
}
+static bool isHeaderElement(Node* a)
+{
+ if (!a)
+ return false;
+
+ return a->hasTagName(h1Tag) ||
+ a->hasTagName(h2Tag) ||
+ a->hasTagName(h3Tag) ||
+ a->hasTagName(h4Tag) ||
+ a->hasTagName(h5Tag);
+}
+
+static bool haveSameTagName(Node* a, Node* b)
+{
+ return a && b && a->isElementNode() && b->isElementNode() && static_cast<Element*>(a)->tagName() == static_cast<Element*>(b)->tagName();
+}
+
bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const VisiblePosition& destination)
{
if (source.isNull() || destination.isNull())
@@ -414,10 +430,12 @@ bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V
Node* sourceNode = source.deepEquivalent().node();
Node* destinationNode = destination.deepEquivalent().node();
Node* sourceBlock = enclosingBlock(sourceNode);
+ Node* destinationBlock = enclosingBlock(destinationNode);
return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode) &&
sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock)) &&
enclosingListChild(sourceBlock) == enclosingListChild(destinationNode) &&
enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent()) &&
+ (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock)) &&
// Don't merge to or from a position before or after a block because it would
// be a no-op and cause infinite recursion.
!isBlock(sourceNode) && !isBlock(destinationNode);
@@ -491,10 +509,11 @@ void ReplaceSelectionCommand::handlePasteAsQuotationNode()
VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent()
{
Node* lastNode = m_lastLeafInserted.get();
- Node* enclosingSelect = enclosingNodeWithTag(Position(lastNode, 0), selectTag);
+ // FIXME: Why is this hack here? What's special about <select> tags?
+ Node* enclosingSelect = enclosingNodeWithTag(firstDeepEditingPositionForNode(lastNode), selectTag);
if (enclosingSelect)
lastNode = enclosingSelect;
- return VisiblePosition(Position(lastNode, maxDeepOffset(lastNode)));
+ return lastDeepEditingPositionForNode(lastNode);
}
VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent()
@@ -509,8 +528,9 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const
{
Node* topNode = fragment.firstChild();
- // Handling this case is more complicated (see handleStyleSpans) and doesn't receive the optimization.
- if (isMailPasteAsQuotationNode(topNode))
+ // 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))
return false;
// Either there are no style spans in the fragment or a WebKit client has added content to the fragment
@@ -573,11 +593,12 @@ void ReplaceSelectionCommand::handleStyleSpans()
RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy();
Node* context = sourceDocumentStyleSpan->parentNode();
- // If Mail wraps the fragment with a Paste as Quotation blockquote, styles from that element are
- // allowed to override those from the source document, see <rdar://problem/4930986>.
- if (isMailPasteAsQuotationNode(context)) {
- RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(context)->copyInheritableProperties();
- RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(context->parentNode())->copyInheritableProperties();
+ // 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);
+ if (blockquoteNode) {
+ RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(blockquoteNode)->copyInheritableProperties();
+ RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(blockquoteNode->parentNode())->copyInheritableProperties();
parentStyle->diff(blockquoteStyle.get());
CSSMutableStyleDeclaration::const_iterator end = blockquoteStyle->end();
@@ -586,7 +607,7 @@ void ReplaceSelectionCommand::handleStyleSpans()
sourceDocumentStyle->removeProperty(property.id());
}
- context = context->parentNode();
+ context = blockquoteNode->parentNode();
}
RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties();
@@ -679,7 +700,7 @@ void ReplaceSelectionCommand::mergeEndIfNeeded()
void ReplaceSelectionCommand::doApply()
{
- Selection selection = endingSelection();
+ VisibleSelection selection = endingSelection();
ASSERT(selection.isCaretOrRange());
ASSERT(selection.start().node());
if (selection.isNone() || !selection.start().node())
@@ -704,9 +725,9 @@ void ReplaceSelectionCommand::doApply()
Position insertionPos = selection.start();
bool startIsInsideMailBlockquote = nearestMailBlockquote(insertionPos.node());
- if (selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote ||
+ if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) ||
startBlock == currentRoot ||
- startBlock && startBlock->renderer() && startBlock->renderer()->isListItem() ||
+ (startBlock && startBlock->renderer() && startBlock->renderer()->isListItem()) ||
selectionIsPlainText)
m_preventNesting = false;
@@ -821,11 +842,20 @@ void ReplaceSelectionCommand::doApply()
fragment.removeNode(refNode);
insertNodeAtAndUpdateNodesInserted(refNode, insertionPos);
-
+
+ // Mutation events (bug 22634) may have already removed the inserted content
+ if (!refNode->inDocument())
+ return;
+
while (node) {
Node* next = node->nextSibling();
fragment.removeNode(node);
insertNodeAfterAndUpdateNodesInserted(node, refNode.get());
+
+ // Mutation events (bug 22634) may have already removed the inserted content
+ if (!node->inDocument())
+ return;
+
refNode = node;
node = next;
}
@@ -846,7 +876,7 @@ void ReplaceSelectionCommand::doApply()
// We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
// didn't have a br after it, so the inserted content ended up in the same paragraph.
- if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.offset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
+ if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.m_offset < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
Position lastPositionToSelect;
@@ -878,9 +908,13 @@ void ReplaceSelectionCommand::doApply()
// Insert a line break just after the inserted content to separate it from what
// comes after and prevent that from happening.
VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
- if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove)
+ if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) {
insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent.deepEquivalent());
-
+ // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move
+ if (!startOfParagraphToMove.deepEquivalent().node()->inDocument())
+ return;
+ }
+
// FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
// only ever used to create positions where inserted content starts/ends.
moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
@@ -1013,9 +1047,9 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi
return;
if (m_selectReplacement)
- setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY));
+ setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY));
else
- setEndingSelection(Selection(end, SEL_DEFAULT_AFFINITY));
+ setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY));
}
EditAction ReplaceSelectionCommand::editingAction() const