ReplaceSelectionCommand.cpp [plain text]
#include "config.h"
#include "ReplaceSelectionCommand.h"
#include "ApplyStyleCommand.h"
#include "BeforeTextInsertedEvent.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "EditingText.h"
#include "Element.h"
#include "Frame.h"
#include "HTMLElement.h"
#include "HTMLInterchange.h"
#include "HTMLNames.h"
#include "SelectionController.h"
#include "TextIterator.h"
#include "htmlediting.h"
#include "markup.h"
#include "visible_units.h"
namespace WebCore {
using namespace HTMLNames;
ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection)
: m_document(document),
m_fragment(fragment),
m_matchStyle(matchStyle),
m_hasInterchangeNewlineAtStart(false),
m_hasInterchangeNewlineAtEnd(false),
m_hasMoreThanOneBlock(false)
{
if (!m_document)
return;
if (!m_fragment)
return;
Node* firstChild = m_fragment->firstChild();
if (!firstChild)
return;
Element* editableRoot = selection.rootEditableElement();
ASSERT(editableRoot);
if (!editableRoot)
return;
Node* styleNode = selection.base().node();
RefPtr<Node> holder = insertFragmentForTestRendering(styleNode);
RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange();
String text = plainText(range.get());
RefPtr<BeforeTextInsertedEvent> evt = new BeforeTextInsertedEvent(text);
ExceptionCode ec = 0;
editableRoot->dispatchEvent(evt, ec, true);
ASSERT(ec == 0);
if (text != evt->text() || !editableRoot->isContentRichlyEditable()) {
restoreTestRenderingNodesToFragment(holder.get());
removeNode(holder);
m_fragment = createFragmentFromText(selection.toRange().get(), evt->text());
firstChild = m_fragment->firstChild();
if (!firstChild)
return;
holder = insertFragmentForTestRendering(styleNode);
}
Node *node = firstChild;
Node *newlineAtStartNode = 0;
Node *newlineAtEndNode = 0;
while (node) {
Node *next = node->traverseNextNode();
if (isInterchangeNewlineNode(node)) {
if (next || node == firstChild) {
m_hasInterchangeNewlineAtStart = true;
newlineAtStartNode = node;
}
else {
m_hasInterchangeNewlineAtEnd = true;
newlineAtEndNode = node;
}
}
else if (isInterchangeConvertedSpaceSpan(node)) {
RefPtr<Node> n = 0;
while ((n = node->firstChild())) {
removeNode(n);
insertNodeBefore(n.get(), node);
}
removeNode(node);
if (n)
next = n->traverseNextNode();
}
node = next;
}
if (newlineAtStartNode)
removeNode(newlineAtStartNode);
if (newlineAtEndNode)
removeNode(newlineAtEndNode);
saveRenderingInfo(holder.get());
removeUnrenderedNodes(holder.get());
m_hasMoreThanOneBlock = renderedBlocks(holder.get()) > 1;
restoreTestRenderingNodesToFragment(holder.get());
removeNode(holder);
removeStyleNodes();
}
bool ReplacementFragment::isEmpty() const
{
return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
}
Node *ReplacementFragment::firstChild() const
{
return m_fragment->firstChild();
}
Node *ReplacementFragment::lastChild() const
{
return m_fragment->lastChild();
}
static bool isMailPasteAsQuotationNode(const Node *node)
{
return node && static_cast<const Element *>(node)->getAttribute("class") == ApplePasteAsQuotation;
}
Node *ReplacementFragment::mergeStartNode() const
{
Node *node = m_fragment->firstChild();
while (node && isBlockFlow(node) && !isMailPasteAsQuotationNode(node))
node = node->traverseNextNode();
return node;
}
bool ReplacementFragment::isInterchangeNewlineNode(const Node *node)
{
static String interchangeNewlineClassString(AppleInterchangeNewline);
return node && node->hasTagName(brTag) &&
static_cast<const Element *>(node)->getAttribute(classAttr) == interchangeNewlineClassString;
}
bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const Node *node)
{
static String convertedSpaceSpanClassString(AppleConvertedSpace);
return node->isHTMLElement() &&
static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString;
}
Node *ReplacementFragment::enclosingBlock(Node *node) const
{
while (node && !isBlockFlow(node))
node = node->parentNode();
return node ? node : m_fragment.get();
}
void ReplacementFragment::removeNodePreservingChildren(Node *node)
{
if (!node)
return;
while (RefPtr<Node> n = node->firstChild()) {
removeNode(n);
insertNodeBefore(n.get(), node);
}
removeNode(node);
}
void ReplacementFragment::removeNode(PassRefPtr<Node> node)
{
if (!node)
return;
Node *parent = node->parentNode();
if (!parent)
return;
ExceptionCode ec = 0;
parent->removeChild(node.get(), ec);
ASSERT(ec == 0);
}
void ReplacementFragment::insertNodeBefore(Node *node, Node *refNode)
{
if (!node || !refNode)
return;
Node *parent = refNode->parentNode();
if (!parent)
return;
ExceptionCode ec = 0;
parent->insertBefore(node, refNode, ec);
ASSERT(ec == 0);
}
PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context)
{
Node* body = m_document->body();
if (!body)
return 0;
RefPtr<StyledElement> holder = static_pointer_cast<StyledElement>(createDefaultParagraphElement(m_document.get()));
ExceptionCode ec = 0;
Node* n = context;
while (n && !n->isElementNode())
n = n->parentNode();
if (n) {
RefPtr<CSSComputedStyleDeclaration> contextStyle = new CSSComputedStyleDeclaration(static_cast<Element*>(n));
CSSStyleDeclaration* style = holder->style();
style->setProperty(CSS_PROP_WHITE_SPACE, contextStyle->getPropertyValue(CSS_PROP_WHITE_SPACE), 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(Node *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);
}
}
bool ReplacementFragment::isBlockFlow(Node* node) const
{
RefPtr<RenderingInfo> info = m_renderingInfo.get(node);
ASSERT(info);
if (!info)
return false;
return info->isBlockFlow();
}
static String &matchNearestBlockquoteColorString()
{
static String matchNearestBlockquoteColorString = "match";
return matchNearestBlockquoteColorString;
}
void ReplaceSelectionCommand::fixupNodeStyles(const NodeVector& nodes, const RenderingInfoMap& renderingInfo)
{
updateLayout();
NodeVector::const_iterator e = nodes.end();
for (NodeVector::const_iterator it = nodes.begin(); it != e; ++it) {
Node *node = (*it).get();
RefPtr<RenderingInfo> info = renderingInfo.get(node);
ASSERT(info);
if (!info)
continue;
CSSMutableStyleDeclaration *desiredStyle = info->style();
ASSERT(desiredStyle);
if (!node->inDocument())
continue;
Position pos(node, 0);
RefPtr<CSSComputedStyleDeclaration> currentStyle = pos.computedStyle();
String matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
if (matchColorCheck == matchNearestBlockquoteColorString()) {
Node *blockquote = nearestMailBlockquote(node);
Position pos(blockquote ? blockquote : node->document()->documentElement(), 0);
RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
String desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
String nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
if (desiredColor != nearestColor)
desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
}
desiredStyle->removeProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
currentStyle->diff(desiredStyle);
if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
desiredStyle->removeBlockProperties();
if (desiredStyle->length() > 0)
applyStyle(desiredStyle, Position(node, 0), Position(node, maxDeepOffset(node)));
}
}
static PassRefPtr<CSSMutableStyleDeclaration> styleForNode(Node *node)
{
if (!node || !node->inDocument())
return 0;
RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(node, 0).computedStyle();
RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
if (Node *blockquote = nearestMailBlockquote(node)) {
RefPtr<CSSComputedStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle();
bool match = (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
if (match) {
style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
return style.release();
}
}
Node *documentElement = node->document()->documentElement();
RefPtr<CSSComputedStyleDeclaration> documentStyle = Position(documentElement, 0).computedStyle();
bool match = (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR));
if (match)
style->setProperty(CSS_PROP__WEBKIT_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
return style.release();
}
void ReplacementFragment::saveRenderingInfo(Node *holder)
{
m_document->updateLayoutIgnorePendingStylesheets();
if (m_matchStyle) {
for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
m_renderingInfo.add(node, new RenderingInfo(0, node->isBlockFlow()));
} else {
for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
m_renderingInfo.add(node, new RenderingInfo(styleForNode(node), node->isBlockFlow()));
m_nodes.append(node);
}
}
}
void ReplacementFragment::removeUnrenderedNodes(Node *holder)
{
DeprecatedPtrList<Node> unrendered;
for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
if (!isNodeRendered(node) && !isTableStructureNode(node))
unrendered.append(node);
}
for (DeprecatedPtrListIterator<Node> it(unrendered); it.current(); ++it)
removeNode(it.current());
}
int ReplacementFragment::renderedBlocks(Node *holder)
{
int count = 0;
Node *prev = 0;
for (Node *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
if (node->isBlockFlow()) {
if (!prev) {
count++;
prev = node;
}
} else {
Node *block = node->enclosingBlockFlowElement();
if (block != prev) {
count++;
prev = block;
}
}
}
return count;
}
void ReplacementFragment::removeStyleNodes()
{
Node *node = m_fragment->firstChild();
while (node) {
Node *next = node->traverseNextNode();
if (node->hasTagName(bTag) ||
node->hasTagName(bigTag) ||
node->hasTagName(centerTag) ||
node->hasTagName(fontTag) ||
node->hasTagName(iTag) ||
node->hasTagName(sTag) ||
node->hasTagName(smallTag) ||
node->hasTagName(strikeTag) ||
node->hasTagName(subTag) ||
node->hasTagName(supTag) ||
node->hasTagName(ttTag) ||
node->hasTagName(uTag) ||
isStyleSpan(node)) {
removeNodePreservingChildren(node);
}
else if (node->isHTMLElement() && !isTabSpanNode(node)) {
HTMLElement *elem = static_cast<HTMLElement *>(node);
CSSMutableStyleDeclaration *inlineStyleDecl = elem->inlineStyleDecl();
if (inlineStyleDecl) {
inlineStyleDecl->removeBlockProperties();
inlineStyleDecl->removeInheritableProperties();
}
}
node = next;
}
}
RenderingInfo::RenderingInfo(PassRefPtr<CSSMutableStyleDeclaration> style, bool isBlockFlow = false)
: m_style(style), m_isBlockFlow(isBlockFlow)
{
}
ReplaceSelectionCommand::ReplaceSelectionCommand(Document *document, DocumentFragment *fragment, bool selectReplacement, bool smartReplace, bool matchStyle, bool forceMergeStart, EditAction editAction)
: CompositeEditCommand(document),
m_selectReplacement(selectReplacement),
m_smartReplace(smartReplace),
m_matchStyle(matchStyle),
m_documentFragment(fragment),
m_forceMergeStart(forceMergeStart),
m_editAction(editAction)
{
}
ReplaceSelectionCommand::~ReplaceSelectionCommand()
{
}
bool ReplaceSelectionCommand::shouldMergeStart(const ReplacementFragment& incomingFragment, const Selection& destinationSelection)
{
if (m_forceMergeStart)
return true;
VisiblePosition visibleStart = destinationSelection.visibleStart();
Node* startBlock = destinationSelection.start().node()->enclosingBlockFlowElement();
if (isStartOfParagraph(visibleStart) && isMailBlockquote(incomingFragment.firstChild()))
return false;
if (enclosingList(incomingFragment.mergeStartNode()) || enclosingNodeWithTag(incomingFragment.mergeStartNode(), tdTag))
return false;
if (startBlock == startBlock->rootEditableElement() && isStartOfBlock(visibleStart) && isEndOfBlock(visibleStart))
return true;
if (!incomingFragment.hasInterchangeNewlineAtStart() &&
(!isStartOfParagraph(visibleStart) || !incomingFragment.hasInterchangeNewlineAtEnd() && !incomingFragment.hasMoreThanOneBlock()))
return true;
return false;
}
bool ReplaceSelectionCommand::shouldMergeEnd(const VisiblePosition& endOfInsertedContent, bool selectionEndWasEndOfParagraph)
{
Node* endNode = endOfInsertedContent.deepEquivalent().node();
Node* nextNode = endOfInsertedContent.next().deepEquivalent().node();
return !selectionEndWasEndOfParagraph &&
isEndOfParagraph(endOfInsertedContent) &&
nearestMailBlockquote(endNode) == nearestMailBlockquote(nextNode) &&
enclosingListChild(endNode) == enclosingListChild(nextNode) &&
enclosingTableCell(endNode) == enclosingTableCell(nextNode) &&
!endNode->hasTagName(hrTag);
}
void ReplaceSelectionCommand::doApply()
{
Selection selection = endingSelection();
ASSERT(selection.isCaretOrRange());
ASSERT(selection.start().node());
if (selection.isNone() || !selection.start().node())
return;
if (!selection.isContentRichlyEditable())
m_matchStyle = true;
Element* currentRoot = selection.rootEditableElement();
ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
if (fragment.isEmpty())
return;
if (m_matchStyle)
m_insertionStyle = styleAtPosition(selection.start());
VisiblePosition visibleStart(selection.start(), selection.affinity());
VisiblePosition visibleEnd(selection.end(), selection.affinity());
bool startAtStartOfBlock = isStartOfBlock(visibleStart);
Node* startBlock = selection.start().node()->enclosingBlockFlowElement();
bool mergeStart = shouldMergeStart(fragment, selection);
bool endWasEndOfParagraph = isEndOfParagraph(visibleEnd);
Position startPos = selection.start();
if (selection.isRange()) {
bool mergeBlocksAfterDelete = isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
deleteSelection(false, mergeBlocksAfterDelete, true);
updateLayout();
visibleStart = endingSelection().visibleStart();
if (fragment.hasInterchangeNewlineAtStart()) {
if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
if (!isEndOfDocument(visibleStart))
setEndingSelection(visibleStart.next());
} else
insertParagraphSeparator();
}
startPos = endingSelection().start();
}
else {
ASSERT(selection.isCaret());
if (fragment.hasInterchangeNewlineAtStart()) {
if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
if (!isEndOfDocument(visibleStart))
setEndingSelection(visibleStart.next());
} else
insertParagraphSeparator();
}
if (!isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
insertParagraphSeparator();
setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
}
startPos = endingSelection().start();
}
Node* endBR = startPos.downstream().node()->hasTagName(brTag) ? startPos.downstream().node() : 0;
if (startAtStartOfBlock && startBlock->inDocument())
startPos = Position(startBlock, 0);
startPos = positionOutsideTabSpan(startPos);
startPos = positionAvoidingSpecialElementBoundary(startPos);
Frame *frame = document()->frame();
frame->clearTypingStyle();
setTypingStyle(0);
if (!fragment.firstChild())
return;
updateLayout();
Position insertionPos = startPos;
if (mergeStart) {
RefPtr<Node> refNode = fragment.mergeStartNode();
if (refNode) {
Node *parent = refNode->parentNode();
RefPtr<Node> node = refNode->nextSibling();
fragment.removeNode(refNode);
insertNodeAtAndUpdateNodesInserted(refNode.get(), startPos.node(), startPos.offset());
while (node && !fragment.isBlockFlow(node.get())) {
Node *next = node->nextSibling();
fragment.removeNode(node);
insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
refNode = node;
node = next;
}
while (parent && parent->parentNode() && parent->childNodeCount() == 0) {
Node *nextParent = parent->parentNode();
fragment.removeNode(parent);
parent = nextParent;
}
}
if (m_lastNodeInserted) {
updateLayout();
insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
}
}
if (fragment.firstChild()) {
RefPtr<Node> refNode = fragment.firstChild();
RefPtr<Node> node = refNode ? refNode->nextSibling() : 0;
Node* insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
Node* insertionRoot = insertionPos.node()->rootEditableElement();
bool insertionBlockIsRoot = insertionBlock == insertionRoot;
VisiblePosition visibleInsertionPos(insertionPos);
fragment.removeNode(refNode);
if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isStartOfBlock(visibleInsertionPos) && !m_lastNodeInserted)
insertNodeBeforeAndUpdateNodesInserted(refNode.get(), insertionBlock);
else if (!insertionBlockIsRoot && fragment.isBlockFlow(refNode.get()) && isEndOfBlock(visibleInsertionPos))
insertNodeAfterAndUpdateNodesInserted(refNode.get(), insertionBlock);
else if (m_lastNodeInserted && !fragment.isBlockFlow(refNode.get())) {
ASSERT(isEndOfParagraph(visibleInsertionPos));
VisiblePosition next = visibleInsertionPos.next();
if (next.isNull() || next.rootEditableElement() != insertionRoot) {
setEndingSelection(visibleInsertionPos);
insertParagraphSeparator();
next = visibleInsertionPos.next();
}
Position pos = next.deepEquivalent().downstream();
insertNodeAtAndUpdateNodesInserted(refNode.get(), pos.node(), pos.offset());
} else {
insertNodeAtAndUpdateNodesInserted(refNode.get(), insertionPos.node(), insertionPos.offset());
}
while (node) {
Node* next = node->nextSibling();
fragment.removeNode(node);
insertNodeAfterAndUpdateNodesInserted(node.get(), refNode.get());
refNode = node;
node = next;
}
updateLayout();
insertionPos = Position(m_lastNodeInserted.get(), m_lastNodeInserted->caretMaxOffset());
}
Position lastPositionToSelect;
bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
bool lastNodeInsertedWasBR = m_lastNodeInserted->hasTagName(brTag);
if (shouldRemoveEndBR(endBR)) {
if (interchangeNewlineAtEnd || lastNodeInsertedWasBR) {
interchangeNewlineAtEnd = false;
lastNodeInsertedWasBR = false;
m_lastNodeInserted = endBR;
lastPositionToSelect = VisiblePosition(Position(m_lastNodeInserted.get(), 0)).deepEquivalent();
} else
removeNodeAndPruneAncestors(endBR);
}
if (!m_matchStyle)
fixupNodeStyles(fragment.nodes(), fragment.renderingInfo());
VisiblePosition endOfInsertedContent(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
VisiblePosition startOfInsertedContent(Position(m_firstNodeInserted.get(), 0));
if (interchangeNewlineAtEnd) {
VisiblePosition pos(insertionPos);
VisiblePosition next = pos.next();
if (endWasEndOfParagraph || !isEndOfParagraph(pos) || next.rootEditableElement() != currentRoot) {
if (!isStartOfParagraph(pos)) {
setEndingSelection(pos.deepEquivalent().downstream(), DOWNSTREAM);
insertParagraphSeparator();
lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
updateNodesInserted(lastPositionToSelect.node());
}
} else {
lastPositionToSelect = next.deepEquivalent().downstream();
}
} else if (lastNodeInsertedWasBR) {
if (!document()->inStrictMode() && isEndOfBlock(VisiblePosition(Position(m_lastNodeInserted.get(), 0))))
insertNodeBeforeAndUpdateNodesInserted(createBreakElement(document()).get(), m_lastNodeInserted.get());
} else if (shouldMergeEnd(endOfInsertedContent, endWasEndOfParagraph)) {
Position downstreamEnd = endOfInsertedContent.deepEquivalent().downstream();
Position upstreamOnePastEnd = endOfInsertedContent.next().deepEquivalent().upstream();
Node* node = downstreamEnd.node();
int offset = downstreamEnd.offset();
if (node && node == upstreamOnePastEnd.node() && offset + 1 == upstreamOnePastEnd.offset() && node->isTextNode()) {
Text* textNode = static_cast<Text*>(node);
if (textNode->length() == 1)
removeNodeAndPruneAncestors(textNode);
else
deleteTextFromNode(textNode, offset, 1);
} else {
bool mergeForward = !inSameParagraph(startOfInsertedContent, endOfInsertedContent);
VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
if (mergeForward)
m_lastNodeInserted = destination.previous().deepEquivalent().node();
}
}
endOfInsertedContent = VisiblePosition(Position(m_lastNodeInserted.get(), maxDeepOffset(m_lastNodeInserted.get())));
startOfInsertedContent = VisiblePosition(Position(m_firstNodeInserted.get(), 0));
if (m_smartReplace) {
bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) &&
!frame->isCharacterSmartReplaceExempt(endOfInsertedContent.characterAfter(), false);
if (needsTrailingSpace) {
RenderObject* renderer = m_lastNodeInserted->renderer();
bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
if (m_lastNodeInserted->isTextNode()) {
Text* text = static_cast<Text*>(m_lastNodeInserted.get());
insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
} else {
RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
insertNodeAfterAndUpdateNodesInserted(node.get(), m_lastNodeInserted.get());
}
}
bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) &&
!frame->isCharacterSmartReplaceExempt(startOfInsertedContent.previous().characterAfter(), true);
if (needsLeadingSpace) {
RenderObject* renderer = m_lastNodeInserted->renderer();
bool collapseWhiteSpace = !renderer || renderer->style()->collapseWhiteSpace();
if (m_firstNodeInserted->isTextNode()) {
Text* text = static_cast<Text*>(m_firstNodeInserted.get());
insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
} else {
RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
insertNodeBefore(node.get(), m_firstNodeInserted.get());
m_firstNodeInserted = node;
}
}
}
completeHTMLReplacement(lastPositionToSelect);
}
bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR)
{
if (!endBR || !endBR->inDocument())
return false;
VisiblePosition visiblePos(Position(endBR, 0));
return
!document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos) ||
isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
}
void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
{
Position start;
Position end;
if (m_firstNodeInserted && m_firstNodeInserted->inDocument() && m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
Node* lastLeaf = m_lastNodeInserted->lastDescendant();
Node* firstLeaf = m_firstNodeInserted->firstDescendant();
start = Position(firstLeaf, 0);
end = Position(lastLeaf, maxDeepOffset(lastLeaf));
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(Selection(start, end, SEL_DEFAULT_AFFINITY));
else
setEndingSelection(end, SEL_DEFAULT_AFFINITY);
}
EditAction ReplaceSelectionCommand::editingAction() const
{
return m_editAction;
}
void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(Node *insertChild, Node *refChild)
{
insertNodeAfter(insertChild, refChild);
updateNodesInserted(insertChild);
}
void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(Node *insertChild, Node *refChild, int offset)
{
insertNodeAt(insertChild, refChild, offset);
updateNodesInserted(insertChild);
}
void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(Node *insertChild, Node *refChild)
{
insertNodeBefore(insertChild, refChild);
updateNodesInserted(insertChild);
}
void ReplaceSelectionCommand::updateNodesInserted(Node *node)
{
if (!node)
return;
m_lastTopNodeInserted = node;
if (!m_firstNodeInserted)
m_firstNodeInserted = node;
if (node == m_lastNodeInserted)
return;
m_lastNodeInserted = node->lastDescendant();
}
}