Index: Source/WebCore/editing/CompositeEditCommand.cpp |
=================================================================== |
--- Source/WebCore/editing/CompositeEditCommand.cpp (revision 109367) |
+++ Source/WebCore/editing/CompositeEditCommand.cpp (working copy) |
@@ -28,6 +28,7 @@ |
#include "AppendNodeCommand.h" |
#include "ApplyStyleCommand.h" |
+#include "DeleteButtonController.h" |
#include "DeleteFromTextNodeCommand.h" |
#include "DeleteSelectionCommand.h" |
#include "Document.h" |
@@ -52,6 +53,7 @@ |
#include "ReplaceSelectionCommand.h" |
#include "RenderBlock.h" |
#include "RenderText.h" |
+#include "ScopedEventQueue.h" |
#include "SetNodeAttributeCommand.h" |
#include "SplitElementCommand.h" |
#include "SplitTextNodeCommand.h" |
@@ -70,37 +72,193 @@ |
using namespace HTMLNames; |
-CompositeEditCommand::CompositeEditCommand(Document *document) |
- : EditCommand(document) |
+PassRefPtr<EditCommandComposition> EditCommandComposition::create(Document* document, |
+ const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) |
{ |
+ return adoptRef(new EditCommandComposition(document, startingSelection, endingSelection, editAction)); |
} |
-CompositeEditCommand::~CompositeEditCommand() |
+EditCommandComposition::EditCommandComposition(Document* document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) |
+ : m_document(document) |
+ , m_startingSelection(startingSelection) |
+ , m_endingSelection(endingSelection) |
+ , m_startingRootEditableElement(startingSelection.rootEditableElement()) |
+ , m_endingRootEditableElement(endingSelection.rootEditableElement()) |
+ , m_editAction(editAction) |
{ |
} |
-void CompositeEditCommand::doUnapply() |
+void EditCommandComposition::unapply() |
{ |
+ ASSERT(m_document); |
+ Frame* frame = m_document->frame(); |
+ ASSERT(frame); |
+ |
+ // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. |
+ // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one |
+ // if one is necessary (like for the creation of VisiblePositions). |
+ m_document->updateLayoutIgnorePendingStylesheets(); |
+ |
+ DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController(); |
+ deleteButtonController->disable(); |
size_t size = m_commands.size(); |
for (size_t i = size; i != 0; --i) |
- m_commands[i - 1]->unapply(); |
+ m_commands[i - 1]->doUnapply(); |
+ deleteButtonController->enable(); |
+ |
+ frame->editor()->unappliedEditing(this); |
} |
-void CompositeEditCommand::doReapply() |
+void EditCommandComposition::reapply() |
{ |
+ ASSERT(m_document); |
+ Frame* frame = m_document->frame(); |
+ ASSERT(frame); |
+ |
+ // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. |
+ // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one |
+ // if one is necessary (like for the creation of VisiblePositions). |
+ m_document->updateLayoutIgnorePendingStylesheets(); |
+ |
+ DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController(); |
+ deleteButtonController->disable(); |
size_t size = m_commands.size(); |
for (size_t i = 0; i != size; ++i) |
- m_commands[i]->reapply(); |
+ m_commands[i]->doReapply(); |
+ deleteButtonController->enable(); |
+ |
+ frame->editor()->reappliedEditing(this); |
} |
+void EditCommandComposition::append(SimpleEditCommand* command) |
+{ |
+ m_commands.append(command); |
+} |
+ |
+void EditCommandComposition::setStartingSelection(const VisibleSelection& selection) |
+{ |
+ m_startingSelection = selection; |
+ m_startingRootEditableElement = selection.rootEditableElement(); |
+} |
+ |
+void EditCommandComposition::setEndingSelection(const VisibleSelection& selection) |
+{ |
+ m_endingSelection = selection; |
+ m_endingRootEditableElement = selection.rootEditableElement(); |
+} |
+ |
+#ifndef NDEBUG |
+void EditCommandComposition::getNodesInCommand(HashSet<Node*>& nodes) |
+{ |
+ size_t size = m_commands.size(); |
+ for (size_t i = 0; i < size; ++i) |
+ m_commands[i]->getNodesInCommand(nodes); |
+} |
+#endif |
+ |
+void applyCommand(PassRefPtr<CompositeEditCommand> command) |
+{ |
+ command->apply(); |
+} |
+ |
+CompositeEditCommand::CompositeEditCommand(Document *document) |
+ : EditCommand(document) |
+{ |
+} |
+ |
+CompositeEditCommand::~CompositeEditCommand() |
+{ |
+ ASSERT(isTopLevelCommand() || !m_composition); |
+} |
+ |
+void CompositeEditCommand::apply() |
+{ |
+ if (!endingSelection().isContentRichlyEditable()) { |
+ switch (editingAction()) { |
+ case EditActionTyping: |
+ case EditActionPaste: |
+ case EditActionDrag: |
+ case EditActionSetWritingDirection: |
+ case EditActionCut: |
+ case EditActionUnspecified: |
+ break; |
+ default: |
+ ASSERT_NOT_REACHED(); |
+ return; |
+ } |
+ } |
+ ensureComposition(); |
+ |
+ // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. |
+ // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one |
+ // if one is necessary (like for the creation of VisiblePositions). |
+ ASSERT(document()); |
+ document()->updateLayoutIgnorePendingStylesheets(); |
+ |
+ Frame* frame = document()->frame(); |
+ ASSERT(frame); |
+ { |
+ EventQueueScope scope; |
+ DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController(); |
+ deleteButtonController->disable(); |
+ doApply(); |
+ deleteButtonController->enable(); |
+ } |
+ |
+ // Only need to call appliedEditing for top-level commands, |
+ // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand). |
+ if (!isTypingCommand()) |
+ frame->editor()->appliedEditing(this); |
+ setShouldRetainAutocorrectionIndicator(false); |
+} |
+ |
+EditCommandComposition* CompositeEditCommand::ensureComposition() |
+{ |
+ CompositeEditCommand* command = this; |
+ while (command && command->parent()) |
+ command = command->parent(); |
+ if (!command->m_composition) |
+ command->m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction()); |
+ return command->m_composition.get(); |
+} |
+ |
+bool CompositeEditCommand::isCreateLinkCommand() const |
+{ |
+ return false; |
+} |
+ |
+bool CompositeEditCommand::preservesTypingStyle() const |
+{ |
+ return false; |
+} |
+ |
+bool CompositeEditCommand::isTypingCommand() const |
+{ |
+ return false; |
+} |
+ |
+bool CompositeEditCommand::shouldRetainAutocorrectionIndicator() const |
+{ |
+ return false; |
+} |
+ |
+void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool) |
+{ |
+} |
+ |
// |
// sugary-sweet convenience functions to help create and apply edit commands in composite commands |
// |
-void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> cmd) |
+void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> prpCommand) |
{ |
- cmd->setParent(this); |
- cmd->apply(); |
- m_commands.append(cmd); |
+ RefPtr<EditCommand> command = prpCommand; |
+ command->setParent(this); |
+ command->doApply(); |
+ if (command->isSimpleEditCommand()) { |
+ command->setParent(0); |
+ ensureComposition()->append(toSimpleEditCommand(command.get())); |
+ } |
+ m_commands.append(command.release()); |
} |
void CompositeEditCommand::applyCommandToComposite(PassRefPtr<CompositeEditCommand> command, const VisibleSelection& selection) |
@@ -110,7 +268,7 @@ |
command->setStartingSelection(selection); |
command->setEndingSelection(selection); |
} |
- command->apply(); |
+ command->doApply(); |
m_commands.append(command); |
} |
@@ -144,6 +302,18 @@ |
applyCommandToComposite(InsertLineBreakCommand::create(document())); |
} |
+bool CompositeEditCommand::isRemovableBlock(const Node* node) |
+{ |
+ Node* parentNode = node->parentNode(); |
+ if ((parentNode && parentNode->firstChild() != parentNode->lastChild()) || !node->hasTagName(divTag)) |
+ return false; |
+ |
+ if (!node->isElementNode() || !toElement(node)->hasAttributes()) |
+ return true; |
+ |
+ return false; |
+} |
+ |
void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild) |
{ |
ASSERT(!refChild->hasTagName(bodyTag)); |
@@ -186,7 +356,7 @@ |
} else if (caretMinOffset(refChild) >= offset) |
insertNodeBefore(insertChild, refChild); |
else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) { |
- splitTextNode(static_cast<Text *>(refChild), offset); |
+ splitTextNode(toText(refChild), offset); |
// Mutation events (bug 22634) from the text node insertion may have removed the refChild |
if (!refChild->inDocument()) |
@@ -362,7 +532,7 @@ |
if (pos.offsetInContainerNode() >= caretMaxOffset(pos.containerNode())) |
return positionInParentAfterNode(tabSpan); |
- splitTextNodeContainingElement(static_cast<Text *>(pos.containerNode()), pos.offsetInContainerNode()); |
+ splitTextNodeContainingElement(toText(pos.containerNode()), pos.offsetInContainerNode()); |
return positionInParentBeforeNode(tabSpan); |
} |
@@ -420,7 +590,7 @@ |
if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode()) |
return false; |
- Text* textNode = static_cast<Text*>(node); |
+ Text* textNode = toText(node); |
if (textNode->length() == 0) |
return false; |
@@ -440,14 +610,14 @@ |
// If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. |
int offset = position.deprecatedEditingOffset(); |
- String text = static_cast<Text*>(node)->data(); |
+ String text = toText(node)->data(); |
if (!isWhitespace(text[offset])) { |
offset--; |
if (offset < 0 || !isWhitespace(text[offset])) |
return; |
} |
- rebalanceWhitespaceOnTextSubstring(static_cast<Text*>(node), position.offsetInContainerNode(), position.offsetInContainerNode()); |
+ rebalanceWhitespaceOnTextSubstring(toText(node), position.offsetInContainerNode(), position.offsetInContainerNode()); |
} |
void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode, int startOffset, int endOffset) |
@@ -489,7 +659,7 @@ |
Node* node = position.deprecatedNode(); |
if (!node || !node->isTextNode()) |
return; |
- Text* textNode = static_cast<Text*>(node); |
+ Text* textNode = toText(node); |
if (textNode->length() == 0) |
return; |
@@ -507,9 +677,9 @@ |
Position previous(previousVisiblePos.deepEquivalent()); |
if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.deprecatedNode()->isTextNode() && !previous.deprecatedNode()->hasTagName(brTag)) |
- replaceTextInNodePreservingMarkers(static_cast<Text*>(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); |
+ replaceTextInNodePreservingMarkers(toText(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); |
if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.deprecatedNode()->isTextNode() && !position.deprecatedNode()->hasTagName(brTag)) |
- replaceTextInNodePreservingMarkers(static_cast<Text*>(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); |
+ replaceTextInNodePreservingMarkers(toText(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); |
} |
void CompositeEditCommand::rebalanceWhitespace() |
@@ -528,6 +698,8 @@ |
if (!textNode || start >= end) |
return; |
+ document()->updateLayout(); |
+ |
RenderText* textRenderer = toRenderText(textNode->renderer()); |
if (!textRenderer) |
return; |
@@ -610,18 +782,20 @@ |
if (comparePositions(start, end) >= 0) |
return; |
- Node* next; |
- for (Node* node = start.deprecatedNode(); node; node = next) { |
- next = node->traverseNextNode(); |
- if (node->isTextNode()) { |
- Text* textNode = static_cast<Text*>(node); |
- int startOffset = node == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0; |
- int endOffset = node == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length()); |
- deleteInsignificantText(textNode, startOffset, endOffset); |
- } |
+ Vector<RefPtr<Text> > nodes; |
+ for (Node* node = start.deprecatedNode(); node; node = node->traverseNextNode()) { |
+ if (node->isTextNode()) |
+ nodes.append(toText(node)); |
if (node == end.deprecatedNode()) |
break; |
} |
+ |
+ for (size_t i = 0; i < nodes.size(); ++i) { |
+ Text* textNode = nodes[i].get(); |
+ int startOffset = textNode == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0; |
+ int endOffset = textNode == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length()); |
+ deleteInsignificantText(textNode, startOffset, endOffset); |
+ } |
} |
void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos) |
@@ -661,7 +835,7 @@ |
if (!container) |
return 0; |
- updateLayout(); |
+ document()->updateLayoutIgnorePendingStylesheets(); |
RenderObject* renderer = container->renderer(); |
if (!renderer || !renderer->isBlockFlow()) |
@@ -687,7 +861,7 @@ |
return; |
} |
- deleteTextFromNode(static_cast<Text*>(p.anchorNode()), p.offsetInContainerNode(), 1); |
+ deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1); |
} |
PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) |
@@ -706,7 +880,7 @@ |
if (pos.isNull()) |
return 0; |
- updateLayout(); |
+ document()->updateLayoutIgnorePendingStylesheets(); |
// It's strange that this function is responsible for verifying that pos has not been invalidated |
// by an earlier call to this function. The caller, applyBlockStyle, should do this. |
@@ -863,7 +1037,7 @@ |
else if (lineBreakExistsAtPosition(position)) { |
// There is a preserved '\n' at caretAfterDelete. |
// We can safely assume this is a text node. |
- Text* textNode = static_cast<Text*>(node); |
+ Text* textNode = toText(node); |
if (textNode->length() == 1) |
removeNodeAndPruneAncestors(node); |
else |
@@ -1016,7 +1190,7 @@ |
// FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. |
insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); |
// Need an updateLayout here in case inserting the br has split a text node. |
- updateLayout(); |
+ document()->updateLayoutIgnorePendingStylesheets(); |
} |
RefPtr<Range> startToDestinationRange(Range::create(document(), firstPositionInNode(document()->documentElement()), destination.deepEquivalent().parentAnchoredEquivalent())); |
@@ -1156,7 +1330,7 @@ |
removeNodeAndPruneAncestors(caretPos.deprecatedNode()); |
else if (caretPos.deprecatedNode()->isTextNode()) { |
ASSERT(caretPos.deprecatedEditingOffset() == 0); |
- Text* textNode = static_cast<Text*>(caretPos.deprecatedNode()); |
+ Text* textNode = toText(caretPos.deprecatedNode()); |
ContainerNode* parentNode = textNode->parentNode(); |
// The preserved newline must be the first thing in the node, since otherwise the previous |
// paragraph would be quoted, and we verified that it wasn't above. |