ReplaceSelectionCommand.cpp [plain text]
#include "config.h"
#include "ReplaceSelectionCommand.h"
#include "ApplyStyleCommand.h"
#include "BeforeTextInsertedEvent.h"
#include "BreakBlockquoteCommand.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "EditingText.h"
#include "Element.h"
#include "EventNames.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "HTMLElement.h"
#include "HTMLInputElement.h"
#include "HTMLInterchange.h"
#include "HTMLNames.h"
#include "NodeList.h"
#include "RenderObject.h"
#include "SmartReplace.h"
#include "TextIterator.h"
#include "htmlediting.h"
#include "markup.h"
#include "visible_units.h"
#include <wtf/StdLibExtras.h>
#include <wtf/Vector.h>
namespace WebCore {
typedef Vector<RefPtr<Node> > NodeVector;
using namespace HTMLNames;
enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
class ReplacementFragment {
WTF_MAKE_NONCOPYABLE(ReplacementFragment);
public:
ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const VisibleSelection&);
Node* firstChild() const;
Node* lastChild() const;
bool isEmpty() const;
bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
void removeNode(PassRefPtr<Node>);
void removeNodePreservingChildren(Node*);
private:
PassRefPtr<StyledElement> insertFragmentForTestRendering(Node* context);
void removeUnrenderedNodes(Node*);
void restoreTestRenderingNodesToFragment(StyledElement*);
void removeInterchangeNodes(Node*);
void insertNodeBefore(PassRefPtr<Node> node, Node* refNode);
RefPtr<Document> m_document;
RefPtr<DocumentFragment> m_fragment;
bool m_matchStyle;
bool m_hasInterchangeNewlineAtStart;
bool m_hasInterchangeNewlineAtEnd;
};
static bool isInterchangeNewlineNode(const Node *node)
{
DEFINE_STATIC_LOCAL(String, interchangeNewlineClassString, (AppleInterchangeNewline));
return node && node->hasTagName(brTag) &&
static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
}
static bool isInterchangeConvertedSpaceSpan(const Node *node)
{
DEFINE_STATIC_LOCAL(String, convertedSpaceSpanClassString, (AppleConvertedSpace));
return node->isHTMLElement() &&
static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
}
static Position positionAvoidingPrecedingNodes(Position pos)
{
if (editingIgnoresContent(pos.deprecatedNode()))
return pos;
Node* stopNode = pos.deprecatedNode()->enclosingBlockFlowElement();
while (stopNode != pos.deprecatedNode() && VisiblePosition(pos) == VisiblePosition(pos.next()))
pos = pos.next();
return pos;
}
ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const VisibleSelection& selection)
: m_document(document),
m_fragment(fragment),
m_matchStyle(matchStyle),
m_hasInterchangeNewlineAtStart(false),
m_hasInterchangeNewlineAtEnd(false)
{
if (!m_document)
return;
if (!m_fragment)
return;
if (!m_fragment->firstChild())
return;
Element* editableRoot = selection.rootEditableElement();
ASSERT(editableRoot);
if (!editableRoot)
return;
Node* shadowAncestorNode = editableRoot->shadowAncestorNode();
if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) &&
!(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) &&
editableRoot->rendererIsRichlyEditable()) {
removeInterchangeNodes(m_fragment.get());
return;
}
Node* styleNode = selection.base().deprecatedNode();
RefPtr<StyledElement> holder = insertFragmentForTestRendering(styleNode);
RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange();
String text = plainText(range.get());
RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
ExceptionCode ec = 0;
editableRoot->dispatchEvent(evt, ec);
ASSERT(ec == 0);
if (text != evt->text() || !editableRoot->rendererIsRichlyEditable()) {
restoreTestRenderingNodesToFragment(holder.get());
removeNode(holder);
m_fragment = createFragmentFromText(selection.toNormalizedRange().get(), evt->text());
if (!m_fragment->firstChild())
return;
holder = insertFragmentForTestRendering(styleNode);
}
removeInterchangeNodes(holder.get());
removeUnrenderedNodes(holder.get());
restoreTestRenderingNodesToFragment(holder.get());
removeNode(holder);
}
bool ReplacementFragment::isEmpty() const
{
return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
}
Node *ReplacementFragment::firstChild() const
{
return m_fragment ? m_fragment->firstChild() : 0;
}
Node *ReplacementFragment::lastChild() const
{
return m_fragment ? m_fragment->lastChild() : 0;
}
void ReplacementFragment::removeNodePreservingChildren(Node *node)
{
if (!node)
return;
while (RefPtr<Node> n = node->firstChild()) {
removeNode(n);
insertNodeBefore(n.release(), node);
}
removeNode(node);
}
void ReplacementFragment::removeNode(PassRefPtr<Node> node)
{
if (!node)
return;
ContainerNode* parent = node->parentNode();
if (!parent)
return;
ExceptionCode ec = 0;
parent->removeChild(node.get(), ec);
ASSERT(ec == 0);
}
void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode)
{
if (!node || !refNode)
return;
ContainerNode* parent = refNode->parentNode();
if (!parent)
return;
ExceptionCode ec = 0;
parent->insertBefore(node, refNode, ec);
ASSERT(ec == 0);
}
PassRefPtr<StyledElement> ReplacementFragment::insertFragmentForTestRendering(Node* context)
{
HTMLElement* body = m_document->body();
if (!body)
return 0;
RefPtr<StyledElement> holder = createDefaultParagraphElement(m_document.get());
ExceptionCode ec = 0;
Node* n = context;
while (n && (!n->isElementNode() || n->hasTagName(brTag)))
n = n->parentNode();
if (n) {
RefPtr<CSSComputedStyleDeclaration> conFontStyle = computedStyle(n);
CSSStyleDeclaration* style = holder->style();
style->setProperty(CSSPropertyWhiteSpace, conFontStyle->getPropertyValue(CSSPropertyWhiteSpace), false, ec);
ASSERT(ec == 0);
style->setProperty(CSSPropertyWebkitUserSelect, conFontStyle->getPropertyValue(CSSPropertyWebkitUserSelect), false, ec);
ASSERT(ec == 0);
}
holder->appendChild(m_fragment, ec);
ASSERT(ec == 0);
body->appendChild(holder.get(), ec);
ASSERT(ec == 0);
m_document->updateLayoutIgnorePendingStylesheets();
return holder.release();
}
void ReplacementFragment::restoreTestRenderingNodesToFragment(StyledElement* holder)
{
if (!holder)
return;
ExceptionCode ec = 0;
while (RefPtr<Node> node = holder->firstChild()) {
holder->removeChild(node.get(), ec);
ASSERT(ec == 0);
m_fragment->appendChild(node.get(), ec);
ASSERT(ec == 0);
}
}
void ReplacementFragment::removeUnrenderedNodes(Node* holder)
{
Vector<RefPtr<Node> > unrendered;
for (Node* node = holder->firstChild(); node; node = node->traverseNextNode(holder))
if (!isNodeRendered(node) && !isTableStructureNode(node))
unrendered.append(node);
size_t n = unrendered.size();
for (size_t i = 0; i < n; ++i)
removeNode(unrendered[i]);
}
void ReplacementFragment::removeInterchangeNodes(Node* container)
{
Node* node = container->firstChild();
while (node) {
if (isInterchangeNewlineNode(node)) {
m_hasInterchangeNewlineAtStart = true;
removeNode(node);
break;
}
node = node->firstChild();
}
if (!container->hasChildNodes())
return;
node = container->lastChild();
while (node) {
if (isInterchangeNewlineNode(node)) {
m_hasInterchangeNewlineAtEnd = true;
removeNode(node);
break;
}
node = node->lastChild();
}
node = container->firstChild();
while (node) {
Node *next = node->traverseNextNode();
if (isInterchangeConvertedSpaceSpan(node)) {
RefPtr<Node> n = 0;
while ((n = node->firstChild())) {
removeNode(n);
insertNodeBefore(n, node);
}
removeNode(node);
if (n)
next = n->traverseNextNode();
}
node = next;
}
}
ReplaceSelectionCommand::ReplaceSelectionCommand(Document* document, PassRefPtr<DocumentFragment> fragment, CommandOptions options, EditAction editAction)
: CompositeEditCommand(document)
, m_selectReplacement(options & SelectReplacement)
, m_smartReplace(options & SmartReplace)
, m_matchStyle(options & MatchStyle)
, m_documentFragment(fragment)
, m_preventNesting(options & PreventNesting)
, m_movingParagraph(options & MovingParagraph)
, m_editAction(editAction)
, m_shouldMergeEnd(false)
{
}
static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisiblePosition endOfInsertedContent)
{
Position existing = endOfExistingContent.deepEquivalent();
Position inserted = endOfInsertedContent.deepEquivalent();
bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote, CanCrossEditingBoundary);
return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted));
}
bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMailBlockquote)
{
if (m_movingParagraph)
return false;
VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBoundary);
if (prev.isNull())
return false;
if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMailBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent()))
return true;
return !selectionStartWasStartOfParagraph
&& !fragmentHasInterchangeNewlineAtStart
&& isStartOfParagraph(startOfInsertedContent)
&& !startOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag)
&& shouldMerge(startOfInsertedContent, prev);
}
bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
{
VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary);
if (next.isNull())
return false;
return !selectionEndWasEndOfParagraph
&& isEndOfParagraph(endOfInsertedContent)
&& !endOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag)
&& shouldMerge(endOfInsertedContent, next);
}
static bool isMailPasteAsQuotationNode(const Node* node)
{
return node && node->hasTagName(blockquoteTag) && node->isElementNode() && static_cast<const Element*>(node)->getAttribute(classAttr) == ApplePasteAsQuotation;
}
void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
{
if (m_firstNodeInserted == node)
m_firstNodeInserted = node->traverseNextNode();
if (m_lastLeafInserted == node)
m_lastLeafInserted = node->lastChild() ? node->lastChild() : node->traverseNextSibling();
CompositeEditCommand::removeNodePreservingChildren(node);
}
void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
{
Node* afterFirst = m_firstNodeInserted ? m_firstNodeInserted->traverseNextSibling() : 0;
Node* afterLast = m_lastLeafInserted ? m_lastLeafInserted->traverseNextSibling() : 0;
CompositeEditCommand::removeNodeAndPruneAncestors(node);
if (m_lastLeafInserted && !m_lastLeafInserted->inDocument())
m_lastLeafInserted = afterLast;
if (m_firstNodeInserted && !m_firstNodeInserted->inDocument())
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())
return false;
Node* sourceNode = source.deepEquivalent().deprecatedNode();
Node* destinationNode = destination.deepEquivalent().deprecatedNode();
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)) &&
!isBlock(sourceNode) && !isBlock(destinationNode);
}
void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance()
{
for (RefPtr<Node> node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
if (isStyleSpan(node.get())) {
HTMLElement* e = toHTMLElement(node.get());
if (isBlock(e))
e->getInlineStyleDecl()->setProperty(CSSPropertyDisplay, CSSValueInline);
if (e->renderer() && e->renderer()->style()->floating() != FNONE)
e->getInlineStyleDecl()->setProperty(CSSPropertyFloat, CSSValueNone);
}
if (node == m_lastLeafInserted)
break;
}
}
void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds()
{
document()->updateLayoutIgnorePendingStylesheets();
if (!m_lastLeafInserted->renderer()
&& m_lastLeafInserted->isTextNode()
&& !enclosingNodeWithTag(firstPositionInOrBeforeNode(m_lastLeafInserted.get()), selectTag)
&& !enclosingNodeWithTag(firstPositionInOrBeforeNode(m_lastLeafInserted.get()), scriptTag)) {
if (m_firstNodeInserted == m_lastLeafInserted) {
removeNode(m_lastLeafInserted.get());
m_lastLeafInserted = 0;
m_firstNodeInserted = 0;
return;
}
RefPtr<Node> previous = m_lastLeafInserted->traversePreviousNode();
removeNode(m_lastLeafInserted.get());
m_lastLeafInserted = previous;
}
if (!m_firstNodeInserted->renderer() &&
m_firstNodeInserted->isTextNode()) {
if (m_firstNodeInserted == m_lastLeafInserted) {
removeNode(m_firstNodeInserted.get());
m_firstNodeInserted = 0;
m_lastLeafInserted = 0;
return;
}
RefPtr<Node> next = m_firstNodeInserted->traverseNextSibling();
removeNode(m_firstNodeInserted.get());
m_firstNodeInserted = next;
}
}
void ReplaceSelectionCommand::handlePasteAsQuotationNode()
{
Node* node = m_firstNodeInserted.get();
if (isMailPasteAsQuotationNode(node))
removeNodeAttribute(static_cast<Element*>(node), classAttr);
}
VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent()
{
Node* lastNode = m_lastLeafInserted.get();
Node* enclosingSelect = enclosingNodeWithTag(firstPositionInOrBeforeNode(lastNode), selectTag);
if (enclosingSelect)
lastNode = enclosingSelect;
return lastPositionInOrAfterNode(lastNode);
}
VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent()
{
return VisiblePosition(nextCandidate(positionInParentBeforeNode(m_firstNodeInserted.get())));
}
static void removeHeadContents(ReplacementFragment& fragment)
{
Node* next = 0;
for (Node* node = fragment.firstChild(); node; node = next) {
if (node->hasTagName(baseTag)
|| node->hasTagName(linkTag)
|| node->hasTagName(metaTag)
|| node->hasTagName(styleTag)
|| node->hasTagName(titleTag)) {
next = node->traverseNextSibling();
fragment.removeNode(node);
} else
next = node->traverseNextNode();
}
}
static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
{
Node* topNode = fragment.firstChild();
if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary))
return false;
if (!isStyleSpan(topNode))
return false;
Node* sourceDocumentStyleSpan = topNode;
RefPtr<Node> copiedRangeStyleSpan = sourceDocumentStyleSpan->firstChild();
RefPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create(insertionPos.parentAnchoredEquivalent());
String styleText = styleAtInsertionPos->style()->cssText();
if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) {
fragment.removeNodePreservingChildren(sourceDocumentStyleSpan);
if (!isStyleSpan(copiedRangeStyleSpan.get()))
return true;
}
if (isStyleSpan(copiedRangeStyleSpan.get()) && styleText == static_cast<Element*>(copiedRangeStyleSpan.get())->getAttribute(styleAttr)) {
fragment.removeNodePreservingChildren(copiedRangeStyleSpan.get());
return true;
}
return false;
}
void ReplaceSelectionCommand::handleStyleSpans()
{
Node* sourceDocumentStyleSpan = 0;
Node* copiedRangeStyleSpan = 0;
for (Node* node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) {
if (isStyleSpan(node)) {
sourceDocumentStyleSpan = node;
if (isStyleSpan(node->firstChild()))
copiedRangeStyleSpan = node->firstChild();
break;
}
}
if (!sourceDocumentStyleSpan)
return;
RefPtr<EditingStyle> sourceDocumentStyle = EditingStyle::create(toHTMLElement(sourceDocumentStyleSpan)->getInlineStyleDecl());
ContainerNode* context = sourceDocumentStyleSpan->parentNode();
Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary);
if (blockquoteNode) {
sourceDocumentStyle->removeStyleConflictingWithStyleOfNode(blockquoteNode);
context = blockquoteNode->parentNode();
}
sourceDocumentStyle->prepareToApplyAt(firstPositionInNode(context));
sourceDocumentStyle->removeBlockProperties();
if (sourceDocumentStyle->isEmpty() && !copiedRangeStyleSpan) {
removeNodePreservingChildren(sourceDocumentStyleSpan);
return;
}
if (!sourceDocumentStyle->isEmpty() && !copiedRangeStyleSpan) {
copyStyleToChildren(sourceDocumentStyleSpan, sourceDocumentStyle->style());
removeNodePreservingChildren(sourceDocumentStyleSpan);
return;
}
RefPtr<EditingStyle> copiedRangeStyle = EditingStyle::create(toHTMLElement(copiedRangeStyleSpan)->getInlineStyleDecl());
copiedRangeStyle->style()->merge(sourceDocumentStyle->style(), false);
removeNodePreservingChildren(sourceDocumentStyleSpan);
context = copiedRangeStyleSpan->parentNode();
copiedRangeStyle->prepareToApplyAt(firstPositionInNode(context));
copiedRangeStyle->removeBlockProperties();
if (copiedRangeStyle->isEmpty()) {
removeNodePreservingChildren(copiedRangeStyleSpan);
return;
}
setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->style()->cssText());
}
void ReplaceSelectionCommand::copyStyleToChildren(Node* parentNode, const CSSMutableStyleDeclaration* parentStyle)
{
ASSERT(parentNode->hasTagName(spanTag));
NodeVector childNodes;
for (RefPtr<Node> childNode = parentNode->firstChild(); childNode; childNode = childNode->nextSibling())
childNodes.append(childNode);
for (NodeVector::const_iterator it = childNodes.begin(); it != childNodes.end(); it++) {
Node* childNode = it->get();
if (childNode->isTextNode() || !isBlock(childNode) || childNode->hasTagName(preTag)) {
RefPtr<Node> newNode = parentNode->cloneNode(false);
ASSERT(newNode->hasTagName(spanTag));
HTMLElement* newSpan = toHTMLElement(newNode.get());
setNodeAttribute(newSpan, styleAttr, parentStyle->cssText());
insertNodeAfter(newSpan, childNode);
ExceptionCode ec = 0;
newSpan->appendChild(childNode, ec);
ASSERT(!ec);
childNode = newSpan;
} else if (childNode->isHTMLElement()) {
RefPtr<CSSMutableStyleDeclaration> newStyle = parentStyle->copy();
HTMLElement* childElement = toHTMLElement(childNode);
RefPtr<CSSMutableStyleDeclaration> existingStyles = childElement->getInlineStyleDecl()->copy();
existingStyles->merge(newStyle.get(), false);
setNodeAttribute(childElement, styleAttr, existingStyles->cssText());
}
}
}
void ReplaceSelectionCommand::mergeEndIfNeeded()
{
if (!m_shouldMergeEnd)
return;
VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
if (m_movingParagraph) {
ASSERT_NOT_REACHED();
return;
}
bool mergeForward = !(inSameParagraph(startOfInsertedContent, endOfInsertedContent) && !isStartOfParagraph(startOfInsertedContent));
VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
if (endOfParagraph(startOfParagraphToMove) == destination) {
RefPtr<Node> placeholder = createBreakElement(document());
insertNodeBefore(placeholder, startOfParagraphToMove.deepEquivalent().deprecatedNode());
destination = VisiblePosition(positionBeforeNode(placeholder.get()));
}
moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
if (mergeForward) {
m_lastLeafInserted = destination.previous().deepEquivalent().deprecatedNode();
if (!m_firstNodeInserted->inDocument())
m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().deprecatedNode();
if (!m_lastLeafInserted)
m_lastLeafInserted = m_firstNodeInserted;
}
}
static Node* enclosingInline(Node* node)
{
while (ContainerNode* parent = node->parentNode()) {
if (parent->isBlockFlow() || parent->hasTagName(bodyTag))
return node;
for (Node* sibling = node->previousSibling(); sibling; sibling = sibling->previousSibling()) {
if (sibling->isBlockFlow())
return node;
}
node = parent;
}
return node;
}
static bool isInlineNodeWithStyle(const Node* node)
{
if (!node->renderer() || !node->renderer()->isInline())
return false;
if (!node->isHTMLElement())
return false;
const HTMLElement* element = static_cast<const HTMLElement*>(node);
AtomicString classAttributeValue = element->getAttribute(classAttr);
if (classAttributeValue == AppleStyleSpanClass
|| classAttributeValue == AppleTabSpanClass
|| classAttributeValue == AppleConvertedSpace
|| classAttributeValue == ApplePasteAsQuotation)
return true;
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();
ASSERT(selection.isCaretOrRange());
ASSERT(selection.start().deprecatedNode());
if (!selection.isNonOrphanedCaretOrRange() || !selection.start().deprecatedNode())
return;
bool selectionIsPlainText = !selection.isContentRichlyEditable();
Element* currentRoot = selection.rootEditableElement();
ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
if (performTrivialReplace(fragment))
return;
if ((selection.start().deprecatedNode()->renderer() && selection.start().deprecatedNode()->renderer()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY)
&& (selection.end().deprecatedNode()->renderer() && selection.end().deprecatedNode()->renderer()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY))
m_matchStyle = false;
if (m_matchStyle) {
m_insertionStyle = EditingStyle::create(selection.start());
m_insertionStyle->mergeTypingStyle(document());
}
VisiblePosition visibleStart = selection.visibleStart();
VisiblePosition visibleEnd = selection.visibleEnd();
bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode());
Position insertionPos = selection.start();
bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary);
if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) ||
startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText)
m_preventNesting = false;
if (selection.isRange()) {
bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
deleteSelection(false, mergeBlocksAfterDelete, true, false);
visibleStart = endingSelection().visibleStart();
if (fragment.hasInterchangeNewlineAtStart()) {
if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
if (!isEndOfDocument(visibleStart))
setEndingSelection(visibleStart.next());
} else
insertParagraphSeparator();
}
insertionPos = endingSelection().start();
} else {
ASSERT(selection.isCaret());
if (fragment.hasInterchangeNewlineAtStart()) {
VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary);
if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull())
setEndingSelection(next);
else
insertParagraphSeparator();
}
if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
insertParagraphSeparator();
setEndingSelection(endingSelection().visibleStart().previous());
}
insertionPos = endingSelection().start();
}
if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) {
applyCommandToComposite(BreakBlockquoteCommand::create(document()));
Node* br = endingSelection().start().deprecatedNode();
ASSERT(br->hasTagName(brTag));
insertionPos = positionInParentBeforeNode(br);
removeNode(br);
}
prepareWhitespaceAtPositionForSplit(insertionPos);
if (!insertionPos.downstream().deprecatedNode())
return;
Node* endBR = insertionPos.downstream().deprecatedNode()->hasTagName(brTag) ? insertionPos.downstream().deprecatedNode() : 0;
VisiblePosition originalVisPosBeforeEndBR;
if (endBR)
originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR), DOWNSTREAM).previous();
startBlock = enclosingBlock(insertionPos.deprecatedNode());
if (m_preventNesting && startBlock && !startIsInsideMailBlockquote) {
ASSERT(startBlock != currentRoot);
VisiblePosition visibleInsertionPos(insertionPos);
if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd()))
insertionPos = positionInParentAfterNode(startBlock);
else if (isStartOfBlock(visibleInsertionPos))
insertionPos = positionInParentBeforeNode(startBlock);
}
insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
if (Frame* frame = document()->frame())
frame->selection()->clearTypingStyle();
removeHeadContents(fragment);
bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
insertionPos = positionAvoidingPrecedingNodes(insertionPos);
insertionPos = positionOutsideTabSpan(insertionPos);
if (!m_matchStyle && !enclosingList(insertionPos.containerNode()) && isStyleSpan(fragment.firstChild())) {
if (insertionPos.containerNode()->isTextNode() && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) {
splitTextNodeContainingElement(static_cast<Text*>(insertionPos.containerNode()), insertionPos.offsetInContainerNode());
insertionPos = firstPositionInNode(insertionPos.containerNode());
}
if (RefPtr<Node> nodeToSplitTo = highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle)) {
if (insertionPos.containerNode() != nodeToSplitTo) {
nodeToSplitTo = splitTreeToNode(insertionPos.anchorNode(), nodeToSplitTo.get(), true).get();
insertionPos = positionInParentBeforeNode(nodeToSplitTo.get());
}
}
}
if (fragment.isEmpty() || !fragment.firstChild())
return;
VisiblePosition startOfInsertedContent, endOfInsertedContent;
RefPtr<Node> refNode = fragment.firstChild();
RefPtr<Node> node = refNode->nextSibling();
fragment.removeNode(refNode);
Node* blockStart = enclosingBlock(insertionPos.deprecatedNode());
if ((isListElement(refNode.get()) || (isStyleSpan(refNode.get()) && isListElement(refNode->firstChild())))
&& blockStart->renderer()->isListItem())
refNode = insertAsListItems(refNode, blockStart, insertionPos);
else
insertNodeAtAndUpdateNodesInserted(refNode, insertionPos);
if (!refNode->inDocument())
return;
bool plainTextFragment = isPlainTextMarkup(refNode.get());
while (node) {
RefPtr<Node> next = node->nextSibling();
fragment.removeNode(node.get());
insertNodeAfterAndUpdateNodesInserted(node, refNode.get());
if (!node->inDocument())
return;
refNode = node;
if (node && plainTextFragment)
plainTextFragment = isPlainTextMarkup(node.get());
node = next;
}
removeUnrenderedTextNodesAtEnds();
negateStyleRulesThatAffectAppearance();
if (!handledStyleSpans)
handleStyleSpans();
if (!m_firstNodeInserted || !m_firstNodeInserted->inDocument())
return;
endOfInsertedContent = positionAtEndOfInsertedContent();
startOfInsertedContent = positionAtStartOfInsertedContent();
if (startBlock && insertionPos.deprecatedNode() == startBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
Position lastPositionToSelect;
bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR)))
removeNodeAndPruneAncestors(endBR);
m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph);
if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote)) {
VisiblePosition destination = startOfInsertedContent.previous();
VisiblePosition startOfParagraphToMove = startOfInsertedContent;
Node* destinationNode = destination.deepEquivalent().deprecatedNode();
if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNode) && enclosingInline(destinationNode)->nextSibling())
insertNodeBefore(createBreakElement(document()), refNode.get());
VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) {
insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent.deepEquivalent());
if (!startOfParagraphToMove.deepEquivalent().anchorNode()->inDocument())
return;
}
moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().downstream().deprecatedNode();
if (!m_lastLeafInserted->inDocument())
m_lastLeafInserted = endingSelection().visibleEnd().deepEquivalent().upstream().deprecatedNode();
}
endOfInsertedContent = positionAtEndOfInsertedContent();
startOfInsertedContent = positionAtStartOfInsertedContent();
if (interchangeNewlineAtEnd) {
VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary);
if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) {
if (!isStartOfParagraph(endOfInsertedContent)) {
setEndingSelection(endOfInsertedContent);
Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEquivalent().deprecatedNode());
if (isListItem(enclosingNode)) {
RefPtr<Node> newListItem = createListItemElement(document());
insertNodeAfter(newListItem, enclosingNode);
setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.get())));
} else
insertParagraphSeparator(true);
lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
updateNodesInserted(lastPositionToSelect.deprecatedNode());
}
} else {
lastPositionToSelect = next.deepEquivalent().downstream();
}
} else
mergeEndIfNeeded();
handlePasteAsQuotationNode();
endOfInsertedContent = positionAtEndOfInsertedContent();
startOfInsertedContent = positionAtStartOfInsertedContent();
if (m_smartReplace && currentRoot) {
Node* start = currentRoot->shadowAncestorNode();
if (start->hasTagName(inputTag) && static_cast<HTMLInputElement*>(start)->isPasswordField())
m_smartReplace = false;
}
if (m_smartReplace) {
bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
!isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
if (needsTrailingSpace) {
RenderObject* renderer = m_lastLeafInserted->renderer();
bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
Node* endNode = positionAtEndOfInsertedContent().deepEquivalent().upstream().deprecatedNode();
if (endNode->isTextNode()) {
Text* text = static_cast<Text*>(endNode);
insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
} else {
RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
insertNodeAfterAndUpdateNodesInserted(node, endNode);
}
}
bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
!isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
if (needsLeadingSpace) {
RenderObject* renderer = m_lastLeafInserted->renderer();
bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
Node* startNode = positionAtStartOfInsertedContent().deepEquivalent().downstream().deprecatedNode();
if (startNode->isTextNode()) {
Text* text = static_cast<Text*>(startNode);
insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
} else {
RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
insertNodeBefore(node, startNode);
m_firstNodeInserted = node;
}
}
}
if (plainTextFragment)
m_matchStyle = false;
completeHTMLReplacement(lastPositionToSelect);
}
bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR)
{
if (!endBR || !endBR->inDocument())
return false;
VisiblePosition visiblePos(positionBeforeNode(endBR));
if (visiblePos.previous() == originalVisPosBeforeEndBR)
return false;
if (!document()->inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
return true;
return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
}
void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
{
Position start;
Position end;
if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastLeafInserted && m_lastLeafInserted->inDocument()) {
start = positionAtStartOfInsertedContent().deepEquivalent();
end = positionAtEndOfInsertedContent().deepEquivalent();
rebalanceWhitespaceAt(start);
rebalanceWhitespaceAt(end);
if (m_matchStyle) {
ASSERT(m_insertionStyle);
applyStyle(m_insertionStyle.get(), start, end);
}
if (lastPositionToSelect.isNotNull())
end = lastPositionToSelect;
} else if (lastPositionToSelect.isNotNull())
start = end = lastPositionToSelect;
else
return;
if (m_selectReplacement)
setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY));
else
setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY));
}
EditAction ReplaceSelectionCommand::editingAction() const
{
return m_editAction;
}
void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(PassRefPtr<Node> insertChild, Node* refChild)
{
Node* nodeToUpdate = insertChild.get(); insertNodeAfter(insertChild, refChild);
updateNodesInserted(nodeToUpdate);
}
void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(PassRefPtr<Node> insertChild, const Position& p)
{
Node* nodeToUpdate = insertChild.get(); insertNodeAt(insertChild, p);
updateNodesInserted(nodeToUpdate);
}
void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(PassRefPtr<Node> insertChild, Node* refChild)
{
Node* nodeToUpdate = insertChild.get(); insertNodeBefore(insertChild, refChild);
updateNodesInserted(nodeToUpdate);
}
Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<Node> listElement, Node* insertionBlock, const Position& insertPos)
{
while (listElement->hasChildNodes() && isListElement(listElement->firstChild()) && listElement->childNodeCount() == 1)
listElement = listElement->firstChild();
bool isStart = isStartOfParagraph(insertPos);
bool isEnd = isEndOfParagraph(insertPos);
bool isMiddle = !isStart && !isEnd;
Node* lastNode = insertionBlock;
if (isMiddle) {
int textNodeOffset = insertPos.offsetInContainerNode();
if (insertPos.deprecatedNode()->isTextNode() && textNodeOffset > 0)
splitTextNode(static_cast<Text*>(insertPos.deprecatedNode()), textNodeOffset);
splitTreeToNode(insertPos.deprecatedNode(), lastNode, true);
}
while (RefPtr<Node> listItem = listElement->firstChild()) {
ExceptionCode ec = 0;
toContainerNode(listElement.get())->removeChild(listItem.get(), ec);
ASSERT(!ec);
if (isStart || isMiddle)
insertNodeBefore(listItem, lastNode);
else if (isEnd) {
insertNodeAfter(listItem, lastNode);
lastNode = listItem.get();
} else
ASSERT_NOT_REACHED();
}
if (isStart || isMiddle)
lastNode = lastNode->previousSibling();
if (isMiddle)
insertNodeAfter(createListItemElement(document()), lastNode);
updateNodesInserted(lastNode);
return lastNode;
}
void ReplaceSelectionCommand::updateNodesInserted(Node *node)
{
if (!node)
return;
if (!m_firstNodeInserted)
m_firstNodeInserted = node;
if (node == m_lastLeafInserted)
return;
m_lastLeafInserted = node->lastDescendant();
}
bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& fragment)
{
if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode())
return false;
if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd())
return false;
Text* textNode = static_cast<Text*>(fragment.firstChild());
String text(textNode->data());
Position start = endingSelection().start().parentAnchoredEquivalent();
Position end = endingSelection().end().parentAnchoredEquivalent();
ASSERT(start.anchorType() == Position::PositionIsOffsetInAnchor);
ASSERT(end.anchorType() == Position::PositionIsOffsetInAnchor);
if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode())
return false;
replaceTextInNode(static_cast<Text*>(start.containerNode()), start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text);
end = Position(start.containerNode(), start.offsetInContainerNode() + text.length(), Position::PositionIsOffsetInAnchor);
VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end);
setEndingSelection(selectionAfterReplace);
return true;
}
}