2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "CompositeEditCommand.h"
29 #include "AppendNodeCommand.h"
30 #include "ApplyStyleCommand.h"
31 #include "DeleteButtonController.h"
32 #include "DeleteFromTextNodeCommand.h"
33 #include "DeleteSelectionCommand.h"
35 #include "DocumentFragment.h"
36 #include "DocumentMarkerController.h"
37 #include "EditorInsertAction.h"
39 #include "HTMLElement.h"
40 #include "HTMLNames.h"
41 #include "InlineTextBox.h"
42 #include "InsertIntoTextNodeCommand.h"
43 #include "InsertLineBreakCommand.h"
44 #include "InsertNodeBeforeCommand.h"
45 #include "InsertParagraphSeparatorCommand.h"
46 #include "InsertTextCommand.h"
47 #include "MergeIdenticalElementsCommand.h"
49 #include "RemoveCSSPropertyCommand.h"
50 #include "RemoveNodeCommand.h"
51 #include "RemoveNodePreservingChildrenCommand.h"
52 #include "ReplaceNodeWithSpanCommand.h"
53 #include "ReplaceSelectionCommand.h"
54 #include "RenderBlock.h"
55 #include "RenderText.h"
56 #include "ScopedEventQueue.h"
57 #include "SetNodeAttributeCommand.h"
58 #include "SplitElementCommand.h"
59 #include "SplitTextNodeCommand.h"
60 #include "SplitTextNodeContainingElementCommand.h"
62 #include "TextIterator.h"
63 #include "WrapContentsInDummySpanCommand.h"
64 #include "htmlediting.h"
66 #include "visible_units.h"
67 #include <wtf/unicode/CharacterNames.h>
73 using namespace HTMLNames;
75 PassRefPtr<EditCommandComposition> EditCommandComposition::create(Document* document,
76 const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
78 return adoptRef(new EditCommandComposition(document, startingSelection, endingSelection, editAction));
81 EditCommandComposition::EditCommandComposition(Document* document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
82 : m_document(document)
83 , m_startingSelection(startingSelection)
84 , m_endingSelection(endingSelection)
85 , m_startingRootEditableElement(startingSelection.rootEditableElement())
86 , m_endingRootEditableElement(endingSelection.rootEditableElement())
87 , m_editAction(editAction)
91 void EditCommandComposition::unapply()
94 Frame* frame = m_document->frame();
97 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
98 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
99 // if one is necessary (like for the creation of VisiblePositions).
100 m_document->updateLayoutIgnorePendingStylesheets();
102 DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController();
103 deleteButtonController->disable();
104 size_t size = m_commands.size();
105 for (size_t i = size; i != 0; --i)
106 m_commands[i - 1]->doUnapply();
107 deleteButtonController->enable();
109 frame->editor()->unappliedEditing(this);
112 void EditCommandComposition::reapply()
115 Frame* frame = m_document->frame();
118 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
119 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
120 // if one is necessary (like for the creation of VisiblePositions).
121 m_document->updateLayoutIgnorePendingStylesheets();
123 DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController();
124 deleteButtonController->disable();
125 size_t size = m_commands.size();
126 for (size_t i = 0; i != size; ++i)
127 m_commands[i]->doReapply();
128 deleteButtonController->enable();
130 frame->editor()->reappliedEditing(this);
133 void EditCommandComposition::append(SimpleEditCommand* command)
135 m_commands.append(command);
138 void EditCommandComposition::setStartingSelection(const VisibleSelection& selection)
140 m_startingSelection = selection;
141 m_startingRootEditableElement = selection.rootEditableElement();
144 void EditCommandComposition::setEndingSelection(const VisibleSelection& selection)
146 m_endingSelection = selection;
147 m_endingRootEditableElement = selection.rootEditableElement();
151 void EditCommandComposition::getNodesInCommand(HashSet<Node*>& nodes)
153 size_t size = m_commands.size();
154 for (size_t i = 0; i < size; ++i)
155 m_commands[i]->getNodesInCommand(nodes);
159 void applyCommand(PassRefPtr<CompositeEditCommand> command)
164 CompositeEditCommand::CompositeEditCommand(Document *document)
165 : EditCommand(document)
169 CompositeEditCommand::~CompositeEditCommand()
171 ASSERT(isTopLevelCommand() || !m_composition);
174 void CompositeEditCommand::apply()
176 if (!endingSelection().isContentRichlyEditable()) {
177 switch (editingAction()) {
178 case EditActionTyping:
179 case EditActionPaste:
181 case EditActionSetWritingDirection:
183 case EditActionUnspecified:
186 ASSERT_NOT_REACHED();
192 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
193 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
194 // if one is necessary (like for the creation of VisiblePositions).
196 document()->updateLayoutIgnorePendingStylesheets();
198 Frame* frame = document()->frame();
201 EventQueueScope scope;
202 DeleteButtonController* deleteButtonController = frame->editor()->deleteButtonController();
203 deleteButtonController->disable();
205 deleteButtonController->enable();
208 // Only need to call appliedEditing for top-level commands,
209 // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand).
210 if (!isTypingCommand())
211 frame->editor()->appliedEditing(this);
212 setShouldRetainAutocorrectionIndicator(false);
215 EditCommandComposition* CompositeEditCommand::ensureComposition()
217 CompositeEditCommand* command = this;
218 while (command && command->parent())
219 command = command->parent();
220 if (!command->m_composition)
221 command->m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction());
222 return command->m_composition.get();
225 bool CompositeEditCommand::isCreateLinkCommand() const
230 bool CompositeEditCommand::preservesTypingStyle() const
235 bool CompositeEditCommand::isTypingCommand() const
240 bool CompositeEditCommand::shouldRetainAutocorrectionIndicator() const
245 void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool)
250 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
252 void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> prpCommand)
254 RefPtr<EditCommand> command = prpCommand;
255 command->setParent(this);
257 if (command->isSimpleEditCommand()) {
258 command->setParent(0);
259 ensureComposition()->append(toSimpleEditCommand(command.get()));
261 m_commands.append(command.release());
264 void CompositeEditCommand::applyCommandToComposite(PassRefPtr<CompositeEditCommand> command, const VisibleSelection& selection)
266 command->setParent(this);
267 if (selection != command->endingSelection()) {
268 command->setStartingSelection(selection);
269 command->setEndingSelection(selection);
272 m_commands.append(command);
275 void CompositeEditCommand::applyStyle(const EditingStyle* style, EditAction editingAction)
277 applyCommandToComposite(ApplyStyleCommand::create(document(), style, editingAction));
280 void CompositeEditCommand::applyStyle(const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction)
282 applyCommandToComposite(ApplyStyleCommand::create(document(), style, start, end, editingAction));
285 void CompositeEditCommand::applyStyledElement(PassRefPtr<Element> element)
287 applyCommandToComposite(ApplyStyleCommand::create(element, false));
290 void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element)
292 applyCommandToComposite(ApplyStyleCommand::create(element, true));
295 void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea)
297 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea));
300 void CompositeEditCommand::insertLineBreak()
302 applyCommandToComposite(InsertLineBreakCommand::create(document()));
305 bool CompositeEditCommand::isRemovableBlock(const Node* node)
307 if (!node->hasTagName(divTag))
310 Node* parentNode = node->parentNode();
311 if (parentNode && parentNode->firstChild() != parentNode->lastChild())
314 if (!toElement(node)->hasAttributes())
320 void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild)
322 ASSERT(!refChild->hasTagName(bodyTag));
323 applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild));
326 void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild)
330 ASSERT(!refChild->hasTagName(bodyTag));
331 ContainerNode* parent = refChild->parentNode();
333 ASSERT(!parent->isShadowRoot());
334 if (parent->lastChild() == refChild)
335 appendNode(insertChild, parent);
337 ASSERT(refChild->nextSibling());
338 insertNodeBefore(insertChild, refChild->nextSibling());
342 void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Position& editingPosition)
344 ASSERT(isEditablePosition(editingPosition));
345 // For editing positions like [table, 0], insert before the table,
346 // likewise for replaced elements, brs, etc.
347 Position p = editingPosition.parentAnchoredEquivalent();
348 Node* refChild = p.deprecatedNode();
349 int offset = p.deprecatedEditingOffset();
351 if (canHaveChildrenForEditing(refChild)) {
352 Node* child = refChild->firstChild();
353 for (int i = 0; child && i < offset; i++)
354 child = child->nextSibling();
356 insertNodeBefore(insertChild, child);
358 appendNode(insertChild, static_cast<Element*>(refChild));
359 } else if (caretMinOffset(refChild) >= offset)
360 insertNodeBefore(insertChild, refChild);
361 else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) {
362 splitTextNode(toText(refChild), offset);
364 // Mutation events (bug 22634) from the text node insertion may have removed the refChild
365 if (!refChild->inDocument())
367 insertNodeBefore(insertChild, refChild);
369 insertNodeAfter(insertChild, refChild);
372 void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<ContainerNode> parent)
374 ASSERT(canHaveChildrenForEditing(parent.get()));
375 applyCommandToComposite(AppendNodeCommand::create(parent, node));
378 void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned from, unsigned to)
380 Vector<RefPtr<Node> > children;
381 Node* child = node->childNode(from);
382 for (unsigned i = from; child && i < to; i++, child = child->nextSibling())
383 children.append(child);
385 size_t size = children.size();
386 for (size_t i = 0; i < size; ++i)
387 removeNode(children[i].release());
390 void CompositeEditCommand::removeNode(PassRefPtr<Node> node)
392 if (!node || !node->nonShadowBoundaryParentNode())
394 applyCommandToComposite(RemoveNodeCommand::create(node));
397 void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node)
399 applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node));
402 void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node)
404 RefPtr<ContainerNode> parent = node->parentNode();
406 prune(parent.release());
409 void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, PassRefPtr<Element> prpNewParent)
411 NodeVector nodesToRemove;
412 RefPtr<Element> newParent = prpNewParent;
414 for (; node && node != pastLastNodeToMove; node = node->nextSibling())
415 nodesToRemove.append(node);
417 for (unsigned i = 0; i < nodesToRemove.size(); i++) {
418 removeNode(nodesToRemove[i]);
419 appendNode(nodesToRemove[i], newParent);
423 void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node* node)
425 int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0;
426 updatePositionForNodeRemoval(position, node);
428 position.moveToOffset(offset);
431 HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node)
433 // It would also be possible to implement all of ReplaceNodeWithSpanCommand
434 // as a series of existing smaller edit commands. Someone who wanted to
435 // reduce the number of edit commands could do so here.
436 RefPtr<ReplaceNodeWithSpanCommand> command = ReplaceNodeWithSpanCommand::create(node);
437 applyCommandToComposite(command);
438 // Returning a raw pointer here is OK because the command is retained by
439 // applyCommandToComposite (thus retaining the span), and the span is also
440 // in the DOM tree, and thus alive whie it has a parent.
441 ASSERT(command->spanElement()->inDocument());
442 return command->spanElement();
445 void CompositeEditCommand::prune(PassRefPtr<Node> node)
447 if (RefPtr<Node> highestNodeToRemove = highestNodeToRemoveInPruning(node.get()))
448 removeNode(highestNodeToRemove.release());
451 void CompositeEditCommand::splitTextNode(PassRefPtr<Text> node, unsigned offset)
453 applyCommandToComposite(SplitTextNodeCommand::create(node, offset));
456 void CompositeEditCommand::splitElement(PassRefPtr<Element> element, PassRefPtr<Node> atChild)
458 applyCommandToComposite(SplitElementCommand::create(element, atChild));
461 void CompositeEditCommand::mergeIdenticalElements(PassRefPtr<Element> prpFirst, PassRefPtr<Element> prpSecond)
463 RefPtr<Element> first = prpFirst;
464 RefPtr<Element> second = prpSecond;
465 ASSERT(!first->isDescendantOf(second.get()) && second != first);
466 if (first->nextSibling() != second) {
468 insertNodeAfter(second, first);
470 applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second));
473 void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr<Element> element)
475 applyCommandToComposite(WrapContentsInDummySpanCommand::create(element));
478 void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr<Text> text, unsigned offset)
480 applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset));
483 void CompositeEditCommand::insertTextIntoNode(PassRefPtr<Text> node, unsigned offset, const String& text)
486 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text));
489 void CompositeEditCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned offset, unsigned count)
491 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count));
494 void CompositeEditCommand::replaceTextInNode(PassRefPtr<Text> node, unsigned offset, unsigned count, const String& replacementText)
496 applyCommandToComposite(DeleteFromTextNodeCommand::create(node.get(), offset, count));
497 if (!replacementText.isEmpty())
498 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText));
501 Position CompositeEditCommand::replaceSelectedTextInNode(const String& text)
503 Position start = endingSelection().start();
504 Position end = endingSelection().end();
505 if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode() || isTabSpanTextNode(start.containerNode()))
508 RefPtr<Text> textNode = start.containerText();
509 replaceTextInNode(textNode, start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text);
511 return Position(textNode.release(), start.offsetInContainerNode() + text.length());
514 static void copyMarkers(const Vector<DocumentMarker*>& markerPointers, Vector<DocumentMarker>& markers)
516 size_t arraySize = markerPointers.size();
517 markers.reserveCapacity(arraySize);
518 for (size_t i = 0; i < arraySize; ++i)
519 markers.append(*markerPointers[i]);
522 void CompositeEditCommand::replaceTextInNodePreservingMarkers(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText)
524 RefPtr<Text> node(prpNode);
525 DocumentMarkerController* markerController = document()->markers();
526 Vector<DocumentMarker> markers;
527 copyMarkers(markerController->markersInRange(Range::create(document(), node, offset, node, offset + count).get(), DocumentMarker::AllMarkers()), markers);
528 replaceTextInNode(node, offset, count, replacementText);
529 RefPtr<Range> newRange = Range::create(document(), node, offset, node, offset + replacementText.length());
530 for (size_t i = 0; i < markers.size(); ++i)
531 markerController->addMarker(newRange.get(), markers[i].type(), markers[i].description());
534 Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
536 if (!isTabSpanTextNode(pos.anchorNode()))
539 switch (pos.anchorType()) {
540 case Position::PositionIsBeforeChildren:
541 case Position::PositionIsAfterChildren:
542 ASSERT_NOT_REACHED();
544 case Position::PositionIsOffsetInAnchor:
546 case Position::PositionIsBeforeAnchor:
547 return positionInParentBeforeNode(pos.anchorNode());
548 case Position::PositionIsAfterAnchor:
549 return positionInParentAfterNode(pos.anchorNode());
552 Node* tabSpan = tabSpanNode(pos.containerNode());
554 if (pos.offsetInContainerNode() <= caretMinOffset(pos.containerNode()))
555 return positionInParentBeforeNode(tabSpan);
557 if (pos.offsetInContainerNode() >= caretMaxOffset(pos.containerNode()))
558 return positionInParentAfterNode(tabSpan);
560 splitTextNodeContainingElement(toText(pos.containerNode()), pos.offsetInContainerNode());
561 return positionInParentBeforeNode(tabSpan);
564 void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, const Position& pos)
566 // insert node before, after, or at split of tab span
567 insertNodeAt(node, positionOutsideTabSpan(pos));
570 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
572 if (endingSelection().isRange())
573 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup));
576 void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
578 if (selection.isRange())
579 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup));
582 void CompositeEditCommand::removeCSSProperty(PassRefPtr<StyledElement> element, CSSPropertyID property)
584 applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), element, property));
587 void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute)
589 setNodeAttribute(element, attribute, AtomicString());
592 void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value)
594 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value));
597 static inline bool containsOnlyWhitespace(const String& text)
599 for (unsigned i = 0; i < text.length(); ++i) {
600 if (!isWhitespace(text.characters()[i]))
607 bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const
609 return containsOnlyWhitespace(text);
612 bool CompositeEditCommand::canRebalance(const Position& position) const
614 Node* node = position.containerNode();
615 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode())
618 Text* textNode = toText(node);
619 if (textNode->length() == 0)
622 RenderObject* renderer = textNode->renderer();
623 if (renderer && !renderer->style()->collapseWhiteSpace())
629 // FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc).
630 void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position)
632 Node* node = position.containerNode();
633 if (!canRebalance(position))
636 // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing.
637 int offset = position.deprecatedEditingOffset();
638 String text = toText(node)->data();
639 if (!isWhitespace(text[offset])) {
641 if (offset < 0 || !isWhitespace(text[offset]))
645 rebalanceWhitespaceOnTextSubstring(toText(node), position.offsetInContainerNode(), position.offsetInContainerNode());
648 void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode, int startOffset, int endOffset)
650 RefPtr<Text> textNode = prpTextNode;
652 String text = textNode->data();
653 ASSERT(!text.isEmpty());
655 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset].
656 int upstream = startOffset;
657 while (upstream > 0 && isWhitespace(text[upstream - 1]))
660 int downstream = endOffset;
661 while ((unsigned)downstream < text.length() && isWhitespace(text[downstream]))
664 int length = downstream - upstream;
668 VisiblePosition visibleUpstreamPos(Position(textNode, upstream));
669 VisiblePosition visibleDownstreamPos(Position(textNode, downstream));
671 String string = text.substring(upstream, length);
672 String rebalancedString = stringWithRebalancedWhitespace(string,
673 // FIXME: Because of the problem mentioned at the top of this function, we must also use nbsps at the start/end of the string because
674 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node.
675 isStartOfParagraph(visibleUpstreamPos) || upstream == 0,
676 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length());
678 if (string != rebalancedString)
679 replaceTextInNodePreservingMarkers(textNode.release(), upstream, length, rebalancedString);
682 void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position)
684 Node* node = position.deprecatedNode();
685 if (!node || !node->isTextNode())
687 Text* textNode = toText(node);
689 if (textNode->length() == 0)
691 RenderObject* renderer = textNode->renderer();
692 if (renderer && !renderer->style()->collapseWhiteSpace())
695 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it.
696 Position upstreamPos = position.upstream();
697 deleteInsignificantText(position.upstream(), position.downstream());
698 position = upstreamPos.downstream();
700 VisiblePosition visiblePos(position);
701 VisiblePosition previousVisiblePos(visiblePos.previous());
702 Position previous(previousVisiblePos.deepEquivalent());
704 if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.deprecatedNode()->isTextNode() && !previous.deprecatedNode()->hasTagName(brTag))
705 replaceTextInNodePreservingMarkers(toText(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
706 if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.deprecatedNode()->isTextNode() && !position.deprecatedNode()->hasTagName(brTag))
707 replaceTextInNodePreservingMarkers(toText(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
710 void CompositeEditCommand::rebalanceWhitespace()
712 VisibleSelection selection = endingSelection();
713 if (selection.isNone())
716 rebalanceWhitespaceAt(selection.start());
717 if (selection.isRange())
718 rebalanceWhitespaceAt(selection.end());
721 void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, unsigned start, unsigned end)
723 if (!textNode || start >= end)
726 document()->updateLayout();
728 RenderText* textRenderer = toRenderText(textNode->renderer());
732 Vector<InlineTextBox*> sortedTextBoxes;
733 size_t sortedTextBoxesPosition = 0;
735 for (InlineTextBox* textBox = textRenderer->firstTextBox(); textBox; textBox = textBox->nextTextBox())
736 sortedTextBoxes.append(textBox);
738 // If there is mixed directionality text, the boxes can be out of order,
739 // (like Arabic with embedded LTR), so sort them first.
740 if (textRenderer->containsReversedText())
741 std::sort(sortedTextBoxes.begin(), sortedTextBoxes.end(), InlineTextBox::compareByStart);
742 InlineTextBox* box = sortedTextBoxes.isEmpty() ? 0 : sortedTextBoxes[sortedTextBoxesPosition];
745 // whole text node is empty
746 removeNode(textNode);
750 unsigned length = textNode->length();
751 if (start >= length || end > length)
754 unsigned removed = 0;
755 InlineTextBox* prevBox = 0;
758 // This loop structure works to process all gaps preceding a box,
759 // and also will look at the gap after the last box.
760 while (prevBox || box) {
761 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0;
763 // No more chance for any intersections
766 unsigned gapEnd = box ? box->start() : length;
767 bool indicesIntersect = start <= gapEnd && end >= gapStart;
768 int gapLen = gapEnd - gapStart;
769 if (indicesIntersect && gapLen > 0) {
770 gapStart = max(gapStart, start);
771 gapEnd = min(gapEnd, end);
773 str = textNode->data().substring(start, end - start);
774 // remove text in the gap
775 str.remove(gapStart - start - removed, gapLen);
781 if (++sortedTextBoxesPosition < sortedTextBoxes.size())
782 box = sortedTextBoxes[sortedTextBoxesPosition];
789 // Replace the text between start and end with our pruned version.
791 replaceTextInNode(textNode, start, end - start, str);
793 // Assert that we are not going to delete all of the text in the node.
794 // If we were, that should have been done above with the call to
795 // removeNode and return.
796 ASSERT(start > 0 || end - start < textNode->length());
797 deleteTextFromNode(textNode, start, end - start);
802 void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end)
804 if (start.isNull() || end.isNull())
807 if (comparePositions(start, end) >= 0)
810 Vector<RefPtr<Text> > nodes;
811 for (Node* node = start.deprecatedNode(); node; node = node->traverseNextNode()) {
812 if (node->isTextNode())
813 nodes.append(toText(node));
814 if (node == end.deprecatedNode())
818 for (size_t i = 0; i < nodes.size(); ++i) {
819 Text* textNode = nodes[i].get();
820 int startOffset = textNode == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0;
821 int endOffset = textNode == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length());
822 deleteInsignificantText(textNode, startOffset, endOffset);
826 void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos)
828 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
829 deleteInsignificantText(pos, end);
832 PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container)
837 document()->updateLayoutIgnorePendingStylesheets();
839 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
840 ASSERT(container->renderer());
842 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
843 appendNode(placeholder, container);
844 return placeholder.release();
847 PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos)
852 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
853 ASSERT(pos.deprecatedNode()->renderer());
855 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
856 insertNodeAt(placeholder, pos);
857 return placeholder.release();
860 PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container)
865 document()->updateLayoutIgnorePendingStylesheets();
867 RenderObject* renderer = container->renderer();
868 if (!renderer || !renderer->isBlockFlow())
871 // append the placeholder to make sure it follows
872 // any unrendered blocks
873 RenderBlock* block = toRenderBlock(renderer);
874 if (block->height() == 0 || (block->isListItem() && block->isEmpty()))
875 return appendBlockPlaceholder(container);
880 // Assumes that the position is at a placeholder and does the removal without much checking.
881 void CompositeEditCommand::removePlaceholderAt(const Position& p)
883 ASSERT(lineBreakExistsAtPosition(p));
885 // We are certain that the position is at a line break, but it may be a br or a preserved newline.
886 if (p.anchorNode()->hasTagName(brTag)) {
887 removeNode(p.anchorNode());
891 deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1);
894 PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position)
896 RefPtr<Element> paragraphElement = createDefaultParagraphElement(document());
898 paragraphElement->appendChild(createBreakElement(document()), ec);
899 insertNodeAt(paragraphElement, position);
900 return paragraphElement.release();
903 // If the paragraph is not entirely within it's own block, create one and move the paragraph into
904 // it, and return that block. Otherwise return 0.
905 PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos)
910 document()->updateLayoutIgnorePendingStylesheets();
912 // It's strange that this function is responsible for verifying that pos has not been invalidated
913 // by an earlier call to this function. The caller, applyBlockStyle, should do this.
914 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
915 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
916 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
917 VisiblePosition next = visibleParagraphEnd.next();
918 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
920 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream();
921 Position upstreamEnd = visibleEnd.deepEquivalent().upstream();
923 // If there are no VisiblePositions in the same block as pos then
924 // upstreamStart will be outside the paragraph
925 if (comparePositions(pos, upstreamStart) < 0)
928 // Perform some checks to see if we need to perform work in this function.
929 if (isBlock(upstreamStart.deprecatedNode())) {
930 // If the block is the root editable element, always move content to a new block,
931 // since it is illegal to modify attributes on the root editable element for editing.
932 if (upstreamStart.deprecatedNode() == editableRootForPosition(upstreamStart)) {
933 // If the block is the root editable element and it contains no visible content, create a new
934 // block but don't try and move content into it, since there's nothing for moveParagraphs to move.
935 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.deprecatedNode()->renderer()))
936 return insertNewDefaultParagraphElementAt(upstreamStart);
937 } else if (isBlock(upstreamEnd.deprecatedNode())) {
938 if (!upstreamEnd.deprecatedNode()->isDescendantOf(upstreamStart.deprecatedNode())) {
939 // If the paragraph end is a descendant of paragraph start, then we need to run
940 // the rest of this function. If not, we can bail here.
943 } else if (enclosingBlock(upstreamEnd.deprecatedNode()) != upstreamStart.deprecatedNode()) {
944 // The visibleEnd. It must be an ancestor of the paragraph start.
945 // We can bail as we have a full block to work with.
946 ASSERT(upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode())));
948 } else if (isEndOfEditableOrNonEditableContent(visibleEnd)) {
949 // At the end of the editable region. We can bail here as well.
954 RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart);
956 bool endWasBr = visibleParagraphEnd.deepEquivalent().deprecatedNode()->hasTagName(brTag);
958 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.get())));
960 if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr)
961 removeNode(newBlock->lastChild());
963 return newBlock.release();
966 void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode)
971 ASSERT(anchorNode->isLink());
973 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode));
974 applyStyledElement(static_cast<Element*>(anchorNode));
975 // Clones of anchorNode have been pushed down, now remove it.
976 if (anchorNode->inDocument())
977 removeNodePreservingChildren(anchorNode);
980 // Clone the paragraph between start and end under blockElement,
981 // preserving the hierarchy up to outerNode.
983 void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* passedOuterNode, Element* blockElement)
985 // First we clone the outerNode
986 RefPtr<Node> lastNode;
987 RefPtr<Node> outerNode = passedOuterNode;
989 if (outerNode->isRootEditableElement()) {
990 lastNode = blockElement;
992 lastNode = outerNode->cloneNode(isTableElement(outerNode.get()));
993 appendNode(lastNode, blockElement);
996 if (start.deprecatedNode() != outerNode && lastNode->isElementNode()) {
997 Vector<RefPtr<Node> > ancestors;
999 // Insert each node from innerNode to outerNode (excluded) in a list.
1000 for (Node* n = start.deprecatedNode(); n && n != outerNode; n = n->parentNode())
1001 ancestors.append(n);
1003 // Clone every node between start.deprecatedNode() and outerBlock.
1005 for (size_t i = ancestors.size(); i != 0; --i) {
1006 Node* item = ancestors[i - 1].get();
1007 RefPtr<Node> child = item->cloneNode(isTableElement(item));
1008 appendNode(child, static_cast<Element *>(lastNode.get()));
1009 lastNode = child.release();
1013 // Handle the case of paragraphs with more than one node,
1014 // cloning all the siblings until end.deprecatedNode() is reached.
1016 if (start.deprecatedNode() != end.deprecatedNode() && !start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) {
1017 // If end is not a descendant of outerNode we need to
1018 // find the first common ancestor to increase the scope
1019 // of our nextSibling traversal.
1020 while (!end.deprecatedNode()->isDescendantOf(outerNode.get())) {
1021 outerNode = outerNode->parentNode();
1024 Node* startNode = start.deprecatedNode();
1025 for (Node* node = startNode->traverseNextSibling(outerNode.get()); node; node = node->traverseNextSibling(outerNode.get())) {
1026 // Move lastNode up in the tree as much as node was moved up in the
1027 // tree by traverseNextSibling, so that the relative depth between
1028 // node and the original start node is maintained in the clone.
1029 while (startNode->parentNode() != node->parentNode()) {
1030 startNode = startNode->parentNode();
1031 lastNode = lastNode->parentNode();
1034 RefPtr<Node> clonedNode = node->cloneNode(true);
1035 insertNodeAfter(clonedNode, lastNode);
1036 lastNode = clonedNode.release();
1037 if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(node))
1044 // There are bugs in deletion when it removes a fully selected table/list.
1045 // It expands and removes the entire table/list, but will let content
1046 // before and after the table/list collapse onto one line.
1047 // Deleting a paragraph will leave a placeholder. Remove it (and prune
1048 // empty or unrendered parents).
1050 void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination)
1052 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
1053 if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
1054 // Note: We want the rightmost candidate.
1055 Position position = caretAfterDelete.deepEquivalent().downstream();
1056 Node* node = position.deprecatedNode();
1057 // Normally deletion will leave a br as a placeholder.
1058 if (node->hasTagName(brTag))
1059 removeNodeAndPruneAncestors(node);
1060 // If the selection to move was empty and in an empty block that
1061 // doesn't require a placeholder to prop itself open (like a bordered
1062 // div or an li), remove it during the move (the list removal code
1063 // expects this behavior).
1064 else if (isBlock(node)) {
1065 // If caret position after deletion and destination position coincides,
1066 // node should not be removed.
1067 if (!position.rendersInDifferentPosition(destination.deepEquivalent())) {
1071 removeNodeAndPruneAncestors(node);
1073 else if (lineBreakExistsAtPosition(position)) {
1074 // There is a preserved '\n' at caretAfterDelete.
1075 // We can safely assume this is a text node.
1076 Text* textNode = toText(node);
1077 if (textNode->length() == 1)
1078 removeNodeAndPruneAncestors(node);
1080 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
1085 // This is a version of moveParagraph that preserves style by keeping the original markup
1086 // It is currently used only by IndentOutdentCommand but it is meant to be used in the
1087 // future by several other commands such as InsertList and the align commands.
1088 // The blockElement parameter is the element to move the paragraph to,
1089 // outerNode is the top element of the paragraph hierarchy.
1091 void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode)
1094 ASSERT(blockElement);
1096 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
1097 VisiblePosition afterParagraph(endOfParagraphToMove.next());
1099 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1100 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1101 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1102 Position end = endOfParagraphToMove.deepEquivalent().upstream();
1104 cloneParagraphUnderNewElement(start, end, outerNode, blockElement);
1106 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1107 deleteSelection(false, false, false, false);
1109 // There are bugs in deletion when it removes a fully selected table/list.
1110 // It expands and removes the entire table/list, but will let content
1111 // before and after the table/list collapse onto one line.
1113 cleanupAfterDeletion();
1115 // Add a br if pruning an empty block level element caused a collapse. For example:
1119 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1120 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1121 // Must recononicalize these two VisiblePositions after the pruning above.
1122 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1123 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1125 if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().deprecatedNode())
1126 && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) {
1127 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1128 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
1133 // This moves a paragraph preserving its style.
1134 void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
1136 ASSERT(isStartOfParagraph(startOfParagraphToMove));
1137 ASSERT(isEndOfParagraph(endOfParagraphToMove));
1138 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle);
1141 void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
1143 if (startOfParagraphToMove == destination)
1146 int startIndex = -1;
1148 int destinationIndex = -1;
1149 bool originalIsDirectional = endingSelection().isDirectional();
1150 if (preserveSelection && !endingSelection().isNone()) {
1151 VisiblePosition visibleStart = endingSelection().visibleStart();
1152 VisiblePosition visibleEnd = endingSelection().visibleEnd();
1154 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
1155 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
1157 if (!startAfterParagraph && !endBeforeParagraph) {
1158 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
1159 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
1162 if (startInParagraph) {
1163 RefPtr<Range> startRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleStart.deepEquivalent().parentAnchoredEquivalent());
1164 startIndex = TextIterator::rangeLength(startRange.get(), true);
1168 if (endInParagraph) {
1169 RefPtr<Range> endRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
1170 endIndex = TextIterator::rangeLength(endRange.get(), true);
1175 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(CannotCrossEditingBoundary);
1176 VisiblePosition afterParagraph(endOfParagraphToMove.next(CannotCrossEditingBoundary));
1178 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1179 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1180 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1181 Position end = endOfParagraphToMove.deepEquivalent().upstream();
1183 // start and end can't be used directly to create a Range; they are "editing positions"
1184 Position startRangeCompliant = start.parentAnchoredEquivalent();
1185 Position endRangeCompliant = end.parentAnchoredEquivalent();
1186 RefPtr<Range> range = Range::create(document(), startRangeCompliant.deprecatedNode(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.deprecatedNode(), endRangeCompliant.deprecatedEditingOffset());
1188 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
1189 // shouldn't matter though, since moved paragraphs will usually be quite small.
1190 RefPtr<DocumentFragment> fragment;
1191 // This used to use a ternary for initialization, but that confused some versions of GCC, see bug 37912
1192 if (startOfParagraphToMove != endOfParagraphToMove)
1193 fragment = createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "");
1195 // A non-empty paragraph's style is moved when we copy and move it. We don't move
1196 // anything if we're given an empty paragraph, but an empty paragraph can have style
1197 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
1198 RefPtr<EditingStyle> styleInEmptyParagraph;
1199 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
1200 styleInEmptyParagraph = EditingStyle::create(startOfParagraphToMove.deepEquivalent());
1201 styleInEmptyParagraph->mergeTypingStyle(document());
1202 // The moved paragraph should assume the block style of the destination.
1203 styleInEmptyParagraph->removeBlockProperties();
1206 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here.
1208 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1209 document()->frame()->editor()->clearMisspellingsAndBadGrammar(endingSelection());
1210 deleteSelection(false, false, false, false);
1212 ASSERT(destination.deepEquivalent().anchorNode()->inDocument());
1213 cleanupAfterDeletion(destination);
1214 ASSERT(destination.deepEquivalent().anchorNode()->inDocument());
1216 // Add a br if pruning an empty block level element caused a collapse. For example:
1220 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1221 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1222 // Must recononicalize these two VisiblePositions after the pruning above.
1223 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1224 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1225 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) {
1226 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1227 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
1228 // Need an updateLayout here in case inserting the br has split a text node.
1229 document()->updateLayoutIgnorePendingStylesheets();
1232 RefPtr<Range> startToDestinationRange(Range::create(document(), firstPositionInNode(document()->documentElement()), destination.deepEquivalent().parentAnchoredEquivalent()));
1233 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true);
1235 setEndingSelection(VisibleSelection(destination, originalIsDirectional));
1236 ASSERT(endingSelection().isCaretOrRange());
1237 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MovingParagraph;
1239 options |= ReplaceSelectionCommand::MatchStyle;
1240 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, options));
1242 document()->frame()->editor()->markMisspellingsAndBadGrammar(endingSelection());
1244 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
1245 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
1246 if (styleInEmptyParagraph && selectionIsEmptyParagraph)
1247 applyStyle(styleInEmptyParagraph.get());
1249 if (preserveSelection && startIndex != -1) {
1250 // Fragment creation (using createMarkup) incorrectly uses regular
1251 // spaces instead of nbsps for some spaces that were rendered (11475), which
1252 // causes spaces to be collapsed during the move operation. This results
1253 // in a call to rangeFromLocationAndLength with a location past the end
1254 // of the document (which will return null).
1255 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true);
1256 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true);
1258 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM, originalIsDirectional));
1262 // FIXME: Send an appropriate shouldDeleteRange call.
1263 bool CompositeEditCommand::breakOutOfEmptyListItem()
1265 RefPtr<Node> emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
1269 RefPtr<EditingStyle> style = EditingStyle::create(endingSelection().start());
1270 style->mergeTypingStyle(document());
1272 RefPtr<ContainerNode> listNode = emptyListItem->parentNode();
1273 // FIXME: Can't we do something better when the immediate parent wasn't a list node?
1275 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag))
1276 || !listNode->rendererIsEditable()
1277 || listNode == emptyListItem->rootEditableElement())
1280 RefPtr<Element> newBlock = 0;
1281 if (ContainerNode* blockEnclosingList = listNode->parentNode()) {
1282 if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item
1283 if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode.get())) {
1284 // If listNode appears at the end of the outer list item, then move listNode outside of this list item
1285 // e.g. <ul><li>hello <ul><li><br></li></ul> </li></ul> should become <ul><li>hello</li> <ul><li><br></li></ul> </ul> after this section
1286 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph.
1287 // e.g. <ul><li> <ul><li><br></li></ul> hello</li></ul> should become <ul><li> <div><br></div> hello</li></ul> at the end
1288 splitElement(static_cast<Element*>(blockEnclosingList), listNode);
1289 removeNodePreservingChildren(listNode->parentNode());
1290 newBlock = createListItemElement(document());
1292 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph.
1293 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag))
1294 newBlock = createListItemElement(document());
1297 newBlock = createDefaultParagraphElement(document());
1299 RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? toElement(emptyListItem.get())->previousElementSibling(): emptyListItem->previousSibling();
1300 RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? toElement(emptyListItem.get())->nextElementSibling(): emptyListItem->nextSibling();
1301 if (isListItem(nextListNode.get()) || isListElement(nextListNode.get())) {
1302 // If emptyListItem follows another list item or nested list, split the list node.
1303 if (isListItem(previousListNode.get()) || isListElement(previousListNode.get()))
1304 splitElement(static_cast<Element*>(listNode.get()), emptyListItem);
1306 // If emptyListItem is followed by other list item or nested list, then insert newBlock before the list node.
1307 // Because we have splitted the element, emptyListItem is the first element in the list node.
1308 // i.e. insert newBlock before ul or ol whose first element is emptyListItem
1309 insertNodeBefore(newBlock, listNode);
1310 removeNode(emptyListItem);
1312 // When emptyListItem does not follow any list item or nested list, insert newBlock after the enclosing list node.
1313 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem.
1314 insertNodeAfter(newBlock, listNode);
1315 removeNode(isListItem(previousListNode.get()) || isListElement(previousListNode.get()) ? emptyListItem.get() : listNode.get());
1318 appendBlockPlaceholder(newBlock);
1319 setEndingSelection(VisibleSelection(firstPositionInNode(newBlock.get()), DOWNSTREAM, endingSelection().isDirectional()));
1321 style->prepareToApplyAt(endingSelection().start());
1322 if (!style->isEmpty())
1323 applyStyle(style.get());
1328 // If the caret is in an empty quoted paragraph, and either there is nothing before that
1329 // paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.
1330 bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
1332 if (!endingSelection().isCaret())
1335 VisiblePosition caret(endingSelection().visibleStart());
1336 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote);
1337 if (!highestBlockquote)
1340 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret))
1343 VisiblePosition previous(caret.previous(CannotCrossEditingBoundary));
1344 // Only move forward if there's nothing before the caret, or if there's unquoted content before it.
1345 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote))
1348 RefPtr<Node> br = createBreakElement(document());
1349 // We want to replace this quoted paragraph with an unquoted one, so insert a br
1350 // to hold the caret before the highest blockquote.
1351 insertNodeBefore(br, highestBlockquote);
1352 VisiblePosition atBR(positionBeforeNode(br.get()));
1353 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert
1355 if (!isStartOfParagraph(atBR))
1356 insertNodeBefore(createBreakElement(document()), br);
1357 setEndingSelection(VisibleSelection(atBR, endingSelection().isDirectional()));
1359 // If this is an empty paragraph there must be a line break here.
1360 if (!lineBreakExistsAtVisiblePosition(caret))
1363 Position caretPos(caret.deepEquivalent().downstream());
1364 // A line break is either a br or a preserved newline.
1365 ASSERT(caretPos.deprecatedNode()->hasTagName(brTag) || (caretPos.deprecatedNode()->isTextNode() && caretPos.deprecatedNode()->renderer()->style()->preserveNewline()));
1367 if (caretPos.deprecatedNode()->hasTagName(brTag))
1368 removeNodeAndPruneAncestors(caretPos.deprecatedNode());
1369 else if (caretPos.deprecatedNode()->isTextNode()) {
1370 ASSERT(caretPos.deprecatedEditingOffset() == 0);
1371 Text* textNode = toText(caretPos.deprecatedNode());
1372 ContainerNode* parentNode = textNode->parentNode();
1373 // The preserved newline must be the first thing in the node, since otherwise the previous
1374 // paragraph would be quoted, and we verified that it wasn't above.
1375 deleteTextFromNode(textNode, 0, 1);
1382 // Operations use this function to avoid inserting content into an anchor when at the start or the end of
1383 // that anchor, as in NSTextView.
1384 // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how
1385 // the caret was made.
1386 Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original)
1388 if (original.isNull())
1391 VisiblePosition visiblePos(original);
1392 Node* enclosingAnchor = enclosingAnchorElement(original);
1393 Position result = original;
1395 if (!enclosingAnchor)
1398 // Don't avoid block level anchors, because that would insert content into the wrong paragraph.
1399 if (enclosingAnchor && !isBlock(enclosingAnchor)) {
1400 VisiblePosition firstInAnchor(firstPositionInNode(enclosingAnchor));
1401 VisiblePosition lastInAnchor(lastPositionInNode(enclosingAnchor));
1402 // If visually just after the anchor, insert *inside* the anchor unless it's the last
1403 // VisiblePosition in the document, to match NSTextView.
1404 if (visiblePos == lastInAnchor) {
1405 // Make sure anchors are pushed down before avoiding them so that we don't
1406 // also avoid structural elements like lists and blocks (5142012).
1407 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1408 pushAnchorElementDown(enclosingAnchor);
1409 enclosingAnchor = enclosingAnchorElement(original);
1410 if (!enclosingAnchor)
1413 // Don't insert outside an anchor if doing so would skip over a line break. It would
1414 // probably be safe to move the line break so that we could still avoid the anchor here.
1415 Position downstream(visiblePos.deepEquivalent().downstream());
1416 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.deprecatedNode()->isDescendantOf(enclosingAnchor))
1419 result = positionInParentAfterNode(enclosingAnchor);
1421 // If visually just before an anchor, insert *outside* the anchor unless it's the first
1422 // VisiblePosition in a paragraph, to match NSTextView.
1423 if (visiblePos == firstInAnchor) {
1424 // Make sure anchors are pushed down before avoiding them so that we don't
1425 // also avoid structural elements like lists and blocks (5142012).
1426 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1427 pushAnchorElementDown(enclosingAnchor);
1428 enclosingAnchor = enclosingAnchorElement(original);
1430 if (!enclosingAnchor)
1433 result = positionInParentBeforeNode(enclosingAnchor);
1437 if (result.isNull() || !editableRootForPosition(result))
1443 // Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
1444 // to determine if the split is necessary. Returns the last split node.
1445 PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor)
1449 ASSERT(start != end);
1452 if (shouldSplitAncestor && end->parentNode())
1453 end = end->parentNode();
1455 RefPtr<Node> endNode = end;
1456 for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) {
1457 if (!node->parentNode()->isElementNode())
1459 // Do not split a node when doing so introduces an empty node.
1460 VisiblePosition positionInParent = firstPositionInNode(node->parentNode());
1461 VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get());
1462 if (positionInParent != positionInNode)
1463 splitElement(static_cast<Element*>(node->parentNode()), node);
1466 return node.release();
1469 PassRefPtr<Element> createBlockPlaceholderElement(Document* document)
1471 RefPtr<Element> breakNode = document->createElement(brTag, false);
1472 return breakNode.release();
1475 } // namespace WebCore