diff options
Diffstat (limited to 'WebCore/editing/ReplaceSelectionCommand.cpp')
-rw-r--r-- | WebCore/editing/ReplaceSelectionCommand.cpp | 84 |
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 |