1/* 2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 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. 12 * 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. 24 */ 25 26#include "config.h" 27#include "CompositeEditCommand.h" 28 29#include "AppendNodeCommand.h" 30#include "ApplyStyleCommand.h" 31#include "DeleteFromTextNodeCommand.h" 32#include "DeleteSelectionCommand.h" 33#include "Document.h" 34#include "DocumentFragment.h" 35#include "DocumentMarkerController.h" 36#include "Editor.h" 37#include "EditorInsertAction.h" 38#include "ExceptionCodePlaceholder.h" 39#include "Frame.h" 40#include "HTMLElement.h" 41#include "HTMLNames.h" 42#include "InlineTextBox.h" 43#include "InsertIntoTextNodeCommand.h" 44#include "InsertLineBreakCommand.h" 45#include "InsertNodeBeforeCommand.h" 46#include "InsertParagraphSeparatorCommand.h" 47#include "InsertTextCommand.h" 48#include "MergeIdenticalElementsCommand.h" 49#include "NodeTraversal.h" 50#include "Range.h" 51#include "RemoveCSSPropertyCommand.h" 52#include "RemoveNodeCommand.h" 53#include "RemoveNodePreservingChildrenCommand.h" 54#include "ReplaceNodeWithSpanCommand.h" 55#include "ReplaceSelectionCommand.h" 56#include "RenderBlock.h" 57#include "RenderText.h" 58#include "ScopedEventQueue.h" 59#include "SetNodeAttributeCommand.h" 60#include "SplitElementCommand.h" 61#include "SplitTextNodeCommand.h" 62#include "SplitTextNodeContainingElementCommand.h" 63#include "Text.h" 64#include "TextIterator.h" 65#include "VisibleUnits.h" 66#include "WrapContentsInDummySpanCommand.h" 67#include "htmlediting.h" 68#include "markup.h" 69#include <wtf/unicode/CharacterNames.h> 70 71#if ENABLE(DELETION_UI) 72#include "DeleteButtonController.h" 73#endif 74 75using namespace std; 76 77namespace WebCore { 78 79using namespace HTMLNames; 80 81PassRefPtr<EditCommandComposition> EditCommandComposition::create(Document* document, 82 const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) 83{ 84 return adoptRef(new EditCommandComposition(document, startingSelection, endingSelection, editAction)); 85} 86 87EditCommandComposition::EditCommandComposition(Document* document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction) 88 : m_document(document) 89 , m_startingSelection(startingSelection) 90 , m_endingSelection(endingSelection) 91 , m_startingRootEditableElement(startingSelection.rootEditableElement()) 92 , m_endingRootEditableElement(endingSelection.rootEditableElement()) 93 , m_editAction(editAction) 94{ 95} 96 97void EditCommandComposition::unapply() 98{ 99 ASSERT(m_document); 100 RefPtr<Frame> frame = m_document->frame(); 101 ASSERT(frame); 102 103 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. 104 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one 105 // if one is necessary (like for the creation of VisiblePositions). 106 m_document->updateLayoutIgnorePendingStylesheets(); 107 108 { 109#if ENABLE(DELETION_UI) 110 DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame.get()); 111#endif 112 113 size_t size = m_commands.size(); 114 for (size_t i = size; i; --i) 115 m_commands[i - 1]->doUnapply(); 116 } 117 118 frame->editor().unappliedEditing(this); 119} 120 121void EditCommandComposition::reapply() 122{ 123 ASSERT(m_document); 124 RefPtr<Frame> frame = m_document->frame(); 125 ASSERT(frame); 126 127 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. 128 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one 129 // if one is necessary (like for the creation of VisiblePositions). 130 m_document->updateLayoutIgnorePendingStylesheets(); 131 132 { 133#if ENABLE(DELETION_UI) 134 DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame.get()); 135#endif 136 size_t size = m_commands.size(); 137 for (size_t i = 0; i != size; ++i) 138 m_commands[i]->doReapply(); 139 } 140 141 frame->editor().reappliedEditing(this); 142} 143 144void EditCommandComposition::append(SimpleEditCommand* command) 145{ 146 m_commands.append(command); 147} 148 149void EditCommandComposition::setStartingSelection(const VisibleSelection& selection) 150{ 151 m_startingSelection = selection; 152 m_startingRootEditableElement = selection.rootEditableElement(); 153} 154 155void EditCommandComposition::setEndingSelection(const VisibleSelection& selection) 156{ 157 m_endingSelection = selection; 158 m_endingRootEditableElement = selection.rootEditableElement(); 159} 160 161#ifndef NDEBUG 162void EditCommandComposition::getNodesInCommand(HashSet<Node*>& nodes) 163{ 164 size_t size = m_commands.size(); 165 for (size_t i = 0; i < size; ++i) 166 m_commands[i]->getNodesInCommand(nodes); 167} 168#endif 169 170void applyCommand(PassRefPtr<CompositeEditCommand> command) 171{ 172 command->apply(); 173} 174 175CompositeEditCommand::CompositeEditCommand(Document *document) 176 : EditCommand(document) 177{ 178} 179 180CompositeEditCommand::~CompositeEditCommand() 181{ 182 ASSERT(isTopLevelCommand() || !m_composition); 183} 184 185void CompositeEditCommand::apply() 186{ 187 if (!endingSelection().isContentRichlyEditable()) { 188 switch (editingAction()) { 189 case EditActionTyping: 190 case EditActionPaste: 191 case EditActionDrag: 192 case EditActionSetWritingDirection: 193 case EditActionCut: 194 case EditActionUnspecified: 195 break; 196 default: 197 ASSERT_NOT_REACHED(); 198 return; 199 } 200 } 201 ensureComposition(); 202 203 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>. 204 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one 205 // if one is necessary (like for the creation of VisiblePositions). 206 ASSERT(document()); 207 document()->updateLayoutIgnorePendingStylesheets(); 208 209 Frame* frame = document()->frame(); 210 ASSERT(frame); 211 { 212 EventQueueScope eventQueueScope; 213#if ENABLE(DELETION_UI) 214 DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame); 215#endif 216 doApply(); 217 } 218 219 // Only need to call appliedEditing for top-level commands, 220 // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand). 221 if (!isTypingCommand()) 222 frame->editor().appliedEditing(this); 223 setShouldRetainAutocorrectionIndicator(false); 224} 225 226EditCommandComposition* CompositeEditCommand::ensureComposition() 227{ 228 CompositeEditCommand* command = this; 229 while (command && command->parent()) 230 command = command->parent(); 231 if (!command->m_composition) 232 command->m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction()); 233 return command->m_composition.get(); 234} 235 236bool CompositeEditCommand::isCreateLinkCommand() const 237{ 238 return false; 239} 240 241bool CompositeEditCommand::preservesTypingStyle() const 242{ 243 return false; 244} 245 246bool CompositeEditCommand::isTypingCommand() const 247{ 248 return false; 249} 250 251bool CompositeEditCommand::shouldRetainAutocorrectionIndicator() const 252{ 253 return false; 254} 255 256void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool) 257{ 258} 259 260// 261// sugary-sweet convenience functions to help create and apply edit commands in composite commands 262// 263void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> prpCommand) 264{ 265 RefPtr<EditCommand> command = prpCommand; 266 command->setParent(this); 267 command->doApply(); 268 if (command->isSimpleEditCommand()) { 269 command->setParent(0); 270 ensureComposition()->append(toSimpleEditCommand(command.get())); 271 } 272 m_commands.append(command.release()); 273} 274 275void CompositeEditCommand::applyCommandToComposite(PassRefPtr<CompositeEditCommand> command, const VisibleSelection& selection) 276{ 277 command->setParent(this); 278 if (selection != command->endingSelection()) { 279 command->setStartingSelection(selection); 280 command->setEndingSelection(selection); 281 } 282 command->doApply(); 283 m_commands.append(command); 284} 285 286void CompositeEditCommand::applyStyle(const EditingStyle* style, EditAction editingAction) 287{ 288 applyCommandToComposite(ApplyStyleCommand::create(document(), style, editingAction)); 289} 290 291void CompositeEditCommand::applyStyle(const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction) 292{ 293 applyCommandToComposite(ApplyStyleCommand::create(document(), style, start, end, editingAction)); 294} 295 296void CompositeEditCommand::applyStyledElement(PassRefPtr<Element> element) 297{ 298 applyCommandToComposite(ApplyStyleCommand::create(element, false)); 299} 300 301void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element) 302{ 303 applyCommandToComposite(ApplyStyleCommand::create(element, true)); 304} 305 306void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea) 307{ 308 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea)); 309} 310 311void CompositeEditCommand::insertLineBreak() 312{ 313 applyCommandToComposite(InsertLineBreakCommand::create(document())); 314} 315 316bool CompositeEditCommand::isRemovableBlock(const Node* node) 317{ 318 if (!node->hasTagName(divTag)) 319 return false; 320 321 Node* parentNode = node->parentNode(); 322 if (parentNode && parentNode->firstChild() != parentNode->lastChild()) 323 return false; 324 325 if (!toElement(node)->hasAttributes()) 326 return true; 327 328 return false; 329} 330 331void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) 332{ 333 ASSERT(!refChild->hasTagName(bodyTag)); 334 applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable)); 335} 336 337void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild) 338{ 339 ASSERT(insertChild); 340 ASSERT(refChild); 341 ASSERT(!refChild->hasTagName(bodyTag)); 342 ContainerNode* parent = refChild->parentNode(); 343 ASSERT(parent); 344 ASSERT(!parent->isShadowRoot()); 345 if (parent->lastChild() == refChild) 346 appendNode(insertChild, parent); 347 else { 348 ASSERT(refChild->nextSibling()); 349 insertNodeBefore(insertChild, refChild->nextSibling()); 350 } 351} 352 353void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Position& editingPosition) 354{ 355 ASSERT(isEditablePosition(editingPosition)); 356 // For editing positions like [table, 0], insert before the table, 357 // likewise for replaced elements, brs, etc. 358 Position p = editingPosition.parentAnchoredEquivalent(); 359 Node* refChild = p.deprecatedNode(); 360 int offset = p.deprecatedEditingOffset(); 361 362 if (canHaveChildrenForEditing(refChild)) { 363 Node* child = refChild->firstChild(); 364 for (int i = 0; child && i < offset; i++) 365 child = child->nextSibling(); 366 if (child) 367 insertNodeBefore(insertChild, child); 368 else 369 appendNode(insertChild, toContainerNode(refChild)); 370 } else if (caretMinOffset(refChild) >= offset) 371 insertNodeBefore(insertChild, refChild); 372 else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) { 373 splitTextNode(toText(refChild), offset); 374 375 // Mutation events (bug 22634) from the text node insertion may have removed the refChild 376 if (!refChild->inDocument()) 377 return; 378 insertNodeBefore(insertChild, refChild); 379 } else 380 insertNodeAfter(insertChild, refChild); 381} 382 383void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<ContainerNode> parent) 384{ 385 ASSERT(canHaveChildrenForEditing(parent.get())); 386 applyCommandToComposite(AppendNodeCommand::create(parent, node)); 387} 388 389void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned from, unsigned to) 390{ 391 Vector<RefPtr<Node> > children; 392 Node* child = node->childNode(from); 393 for (unsigned i = from; child && i < to; i++, child = child->nextSibling()) 394 children.append(child); 395 396 size_t size = children.size(); 397 for (size_t i = 0; i < size; ++i) 398 removeNode(children[i].release()); 399} 400 401void CompositeEditCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) 402{ 403 if (!node || !node->nonShadowBoundaryParentNode()) 404 return; 405 applyCommandToComposite(RemoveNodeCommand::create(node, shouldAssumeContentIsAlwaysEditable)); 406} 407 408void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) 409{ 410 applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable)); 411} 412 413void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node) 414{ 415 RefPtr<ContainerNode> parent = node->parentNode(); 416 removeNode(node); 417 prune(parent.release()); 418} 419 420void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, PassRefPtr<Element> prpNewParent) 421{ 422 NodeVector nodesToRemove; 423 RefPtr<Element> newParent = prpNewParent; 424 425 for (; node && node != pastLastNodeToMove; node = node->nextSibling()) 426 nodesToRemove.append(node); 427 428 for (unsigned i = 0; i < nodesToRemove.size(); i++) { 429 removeNode(nodesToRemove[i]); 430 appendNode(nodesToRemove[i], newParent); 431 } 432} 433 434void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node* node) 435{ 436 int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0; 437 updatePositionForNodeRemoval(position, node); 438 if (offset) 439 position.moveToOffset(offset); 440} 441 442HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node) 443{ 444 // It would also be possible to implement all of ReplaceNodeWithSpanCommand 445 // as a series of existing smaller edit commands. Someone who wanted to 446 // reduce the number of edit commands could do so here. 447 RefPtr<ReplaceNodeWithSpanCommand> command = ReplaceNodeWithSpanCommand::create(node); 448 applyCommandToComposite(command); 449 // Returning a raw pointer here is OK because the command is retained by 450 // applyCommandToComposite (thus retaining the span), and the span is also 451 // in the DOM tree, and thus alive whie it has a parent. 452 ASSERT(command->spanElement()->inDocument()); 453 return command->spanElement(); 454} 455 456void CompositeEditCommand::prune(PassRefPtr<Node> node) 457{ 458 if (RefPtr<Node> highestNodeToRemove = highestNodeToRemoveInPruning(node.get())) 459 removeNode(highestNodeToRemove.release()); 460} 461 462void CompositeEditCommand::splitTextNode(PassRefPtr<Text> node, unsigned offset) 463{ 464 applyCommandToComposite(SplitTextNodeCommand::create(node, offset)); 465} 466 467void CompositeEditCommand::splitElement(PassRefPtr<Element> element, PassRefPtr<Node> atChild) 468{ 469 applyCommandToComposite(SplitElementCommand::create(element, atChild)); 470} 471 472void CompositeEditCommand::mergeIdenticalElements(PassRefPtr<Element> prpFirst, PassRefPtr<Element> prpSecond) 473{ 474 RefPtr<Element> first = prpFirst; 475 RefPtr<Element> second = prpSecond; 476 ASSERT(!first->isDescendantOf(second.get()) && second != first); 477 if (first->nextSibling() != second) { 478 removeNode(second); 479 insertNodeAfter(second, first); 480 } 481 applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second)); 482} 483 484void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr<Element> element) 485{ 486 applyCommandToComposite(WrapContentsInDummySpanCommand::create(element)); 487} 488 489void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr<Text> text, unsigned offset) 490{ 491 applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset)); 492} 493 494void CompositeEditCommand::insertTextIntoNode(PassRefPtr<Text> node, unsigned offset, const String& text) 495{ 496 if (!text.isEmpty()) 497 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text)); 498} 499 500void CompositeEditCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned offset, unsigned count) 501{ 502 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count)); 503} 504 505void CompositeEditCommand::replaceTextInNode(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText) 506{ 507 RefPtr<Text> node(prpNode); 508 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count)); 509 if (!replacementText.isEmpty()) 510 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText)); 511} 512 513Position CompositeEditCommand::replaceSelectedTextInNode(const String& text) 514{ 515 Position start = endingSelection().start(); 516 Position end = endingSelection().end(); 517 if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode() || isTabSpanTextNode(start.containerNode())) 518 return Position(); 519 520 RefPtr<Text> textNode = start.containerText(); 521 replaceTextInNode(textNode, start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text); 522 523 return Position(textNode.release(), start.offsetInContainerNode() + text.length()); 524} 525 526static void copyMarkers(const Vector<DocumentMarker*>& markerPointers, Vector<DocumentMarker>& markers) 527{ 528 size_t arraySize = markerPointers.size(); 529 markers.reserveCapacity(arraySize); 530 for (size_t i = 0; i < arraySize; ++i) 531 markers.append(*markerPointers[i]); 532} 533 534void CompositeEditCommand::replaceTextInNodePreservingMarkers(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText) 535{ 536 RefPtr<Text> node(prpNode); 537 DocumentMarkerController* markerController = document()->markers(); 538 Vector<DocumentMarker> markers; 539 copyMarkers(markerController->markersInRange(Range::create(document(), node, offset, node, offset + count).get(), DocumentMarker::AllMarkers()), markers); 540 replaceTextInNode(node, offset, count, replacementText); 541 RefPtr<Range> newRange = Range::create(document(), node, offset, node, offset + replacementText.length()); 542 for (size_t i = 0; i < markers.size(); ++i) 543 markerController->addMarker(newRange.get(), markers[i].type(), markers[i].description()); 544} 545 546Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos) 547{ 548 if (!isTabSpanTextNode(pos.anchorNode())) 549 return pos; 550 551 switch (pos.anchorType()) { 552 case Position::PositionIsBeforeChildren: 553 case Position::PositionIsAfterChildren: 554 ASSERT_NOT_REACHED(); 555 return pos; 556 case Position::PositionIsOffsetInAnchor: 557 break; 558 case Position::PositionIsBeforeAnchor: 559 return positionInParentBeforeNode(pos.anchorNode()); 560 case Position::PositionIsAfterAnchor: 561 return positionInParentAfterNode(pos.anchorNode()); 562 } 563 564 Node* tabSpan = tabSpanNode(pos.containerNode()); 565 566 if (pos.offsetInContainerNode() <= caretMinOffset(pos.containerNode())) 567 return positionInParentBeforeNode(tabSpan); 568 569 if (pos.offsetInContainerNode() >= caretMaxOffset(pos.containerNode())) 570 return positionInParentAfterNode(tabSpan); 571 572 splitTextNodeContainingElement(toText(pos.containerNode()), pos.offsetInContainerNode()); 573 return positionInParentBeforeNode(tabSpan); 574} 575 576void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, const Position& pos) 577{ 578 // insert node before, after, or at split of tab span 579 insertNodeAt(node, positionOutsideTabSpan(pos)); 580} 581 582void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) 583{ 584 if (endingSelection().isRange()) 585 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup)); 586} 587 588void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup) 589{ 590 if (selection.isRange()) 591 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup)); 592} 593 594void CompositeEditCommand::removeCSSProperty(PassRefPtr<StyledElement> element, CSSPropertyID property) 595{ 596 applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), element, property)); 597} 598 599void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute) 600{ 601 setNodeAttribute(element, attribute, AtomicString()); 602} 603 604void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value) 605{ 606 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value)); 607} 608 609static inline bool containsOnlyWhitespace(const String& text) 610{ 611 for (unsigned i = 0; i < text.length(); ++i) { 612 if (!isWhitespace(text.characters()[i])) 613 return false; 614 } 615 616 return true; 617} 618 619bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const 620{ 621 return containsOnlyWhitespace(text); 622} 623 624bool CompositeEditCommand::canRebalance(const Position& position) const 625{ 626 Node* node = position.containerNode(); 627 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode()) 628 return false; 629 630 Text* textNode = toText(node); 631 if (textNode->length() == 0) 632 return false; 633 634 RenderObject* renderer = textNode->renderer(); 635 if (renderer && !renderer->style()->collapseWhiteSpace()) 636 return false; 637 638 return true; 639} 640 641// FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc). 642void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) 643{ 644 Node* node = position.containerNode(); 645 if (!canRebalance(position)) 646 return; 647 648 // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. 649 int offset = position.deprecatedEditingOffset(); 650 String text = toText(node)->data(); 651 if (!isWhitespace(text[offset])) { 652 offset--; 653 if (offset < 0 || !isWhitespace(text[offset])) 654 return; 655 } 656 657 rebalanceWhitespaceOnTextSubstring(toText(node), position.offsetInContainerNode(), position.offsetInContainerNode()); 658} 659 660void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode, int startOffset, int endOffset) 661{ 662 RefPtr<Text> textNode = prpTextNode; 663 664 String text = textNode->data(); 665 ASSERT(!text.isEmpty()); 666 667 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset]. 668 int upstream = startOffset; 669 while (upstream > 0 && isWhitespace(text[upstream - 1])) 670 upstream--; 671 672 int downstream = endOffset; 673 while ((unsigned)downstream < text.length() && isWhitespace(text[downstream])) 674 downstream++; 675 676 int length = downstream - upstream; 677 if (!length) 678 return; 679 680 VisiblePosition visibleUpstreamPos(Position(textNode, upstream)); 681 VisiblePosition visibleDownstreamPos(Position(textNode, downstream)); 682 683 String string = text.substring(upstream, length); 684 String rebalancedString = stringWithRebalancedWhitespace(string, 685 // 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 686 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node. 687 isStartOfParagraph(visibleUpstreamPos) || upstream == 0, 688 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length()); 689 690 if (string != rebalancedString) 691 replaceTextInNodePreservingMarkers(textNode.release(), upstream, length, rebalancedString); 692} 693 694void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position) 695{ 696 Node* node = position.deprecatedNode(); 697 if (!node || !node->isTextNode()) 698 return; 699 Text* textNode = toText(node); 700 701 if (textNode->length() == 0) 702 return; 703 RenderObject* renderer = textNode->renderer(); 704 if (renderer && !renderer->style()->collapseWhiteSpace()) 705 return; 706 707 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it. 708 Position upstreamPos = position.upstream(); 709 deleteInsignificantText(position.upstream(), position.downstream()); 710 position = upstreamPos.downstream(); 711 712 VisiblePosition visiblePos(position); 713 VisiblePosition previousVisiblePos(visiblePos.previous()); 714 Position previous(previousVisiblePos.deepEquivalent()); 715 716 if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.deprecatedNode()->isTextNode() && !previous.deprecatedNode()->hasTagName(brTag)) 717 replaceTextInNodePreservingMarkers(toText(previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 718 if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.deprecatedNode()->isTextNode() && !position.deprecatedNode()->hasTagName(brTag)) 719 replaceTextInNodePreservingMarkers(toText(position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 720} 721 722void CompositeEditCommand::rebalanceWhitespace() 723{ 724 VisibleSelection selection = endingSelection(); 725 if (selection.isNone()) 726 return; 727 728 rebalanceWhitespaceAt(selection.start()); 729 if (selection.isRange()) 730 rebalanceWhitespaceAt(selection.end()); 731} 732 733void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, unsigned start, unsigned end) 734{ 735 if (!textNode || start >= end) 736 return; 737 738 document()->updateLayout(); 739 740 RenderText* textRenderer = toRenderText(textNode->renderer()); 741 if (!textRenderer) 742 return; 743 744 Vector<InlineTextBox*> sortedTextBoxes; 745 size_t sortedTextBoxesPosition = 0; 746 747 for (InlineTextBox* textBox = textRenderer->firstTextBox(); textBox; textBox = textBox->nextTextBox()) 748 sortedTextBoxes.append(textBox); 749 750 // If there is mixed directionality text, the boxes can be out of order, 751 // (like Arabic with embedded LTR), so sort them first. 752 if (textRenderer->containsReversedText()) 753 std::sort(sortedTextBoxes.begin(), sortedTextBoxes.end(), InlineTextBox::compareByStart); 754 InlineTextBox* box = sortedTextBoxes.isEmpty() ? 0 : sortedTextBoxes[sortedTextBoxesPosition]; 755 756 if (!box) { 757 // whole text node is empty 758 removeNode(textNode); 759 return; 760 } 761 762 unsigned length = textNode->length(); 763 if (start >= length || end > length) 764 return; 765 766 unsigned removed = 0; 767 InlineTextBox* prevBox = 0; 768 String str; 769 770 // This loop structure works to process all gaps preceding a box, 771 // and also will look at the gap after the last box. 772 while (prevBox || box) { 773 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0; 774 if (end < gapStart) 775 // No more chance for any intersections 776 break; 777 778 unsigned gapEnd = box ? box->start() : length; 779 bool indicesIntersect = start <= gapEnd && end >= gapStart; 780 int gapLen = gapEnd - gapStart; 781 if (indicesIntersect && gapLen > 0) { 782 gapStart = max(gapStart, start); 783 gapEnd = min(gapEnd, end); 784 if (str.isNull()) 785 str = textNode->data().substring(start, end - start); 786 // remove text in the gap 787 str.remove(gapStart - start - removed, gapLen); 788 removed += gapLen; 789 } 790 791 prevBox = box; 792 if (box) { 793 if (++sortedTextBoxesPosition < sortedTextBoxes.size()) 794 box = sortedTextBoxes[sortedTextBoxesPosition]; 795 else 796 box = 0; 797 } 798 } 799 800 if (!str.isNull()) { 801 // Replace the text between start and end with our pruned version. 802 if (!str.isEmpty()) 803 replaceTextInNode(textNode, start, end - start, str); 804 else { 805 // Assert that we are not going to delete all of the text in the node. 806 // If we were, that should have been done above with the call to 807 // removeNode and return. 808 ASSERT(start > 0 || end - start < textNode->length()); 809 deleteTextFromNode(textNode, start, end - start); 810 } 811 } 812} 813 814void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end) 815{ 816 if (start.isNull() || end.isNull()) 817 return; 818 819 if (comparePositions(start, end) >= 0) 820 return; 821 822 Vector<RefPtr<Text> > nodes; 823 for (Node* node = start.deprecatedNode(); node; node = NodeTraversal::next(node)) { 824 if (node->isTextNode()) 825 nodes.append(toText(node)); 826 if (node == end.deprecatedNode()) 827 break; 828 } 829 830 for (size_t i = 0; i < nodes.size(); ++i) { 831 Text* textNode = nodes[i].get(); 832 int startOffset = textNode == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0; 833 int endOffset = textNode == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length()); 834 deleteInsignificantText(textNode, startOffset, endOffset); 835 } 836} 837 838void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos) 839{ 840 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(); 841 deleteInsignificantText(pos, end); 842} 843 844PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container) 845{ 846 if (!container) 847 return 0; 848 849 document()->updateLayoutIgnorePendingStylesheets(); 850 851 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. 852 ASSERT(container->renderer()); 853 854 RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); 855 appendNode(placeholder, container); 856 return placeholder.release(); 857} 858 859PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos) 860{ 861 if (pos.isNull()) 862 return 0; 863 864 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. 865 ASSERT(pos.deprecatedNode()->renderer()); 866 867 RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); 868 insertNodeAt(placeholder, pos); 869 return placeholder.release(); 870} 871 872PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container) 873{ 874 if (!container) 875 return 0; 876 877 document()->updateLayoutIgnorePendingStylesheets(); 878 879 RenderObject* renderer = container->renderer(); 880 if (!renderer || !renderer->isBlockFlow()) 881 return 0; 882 883 // append the placeholder to make sure it follows 884 // any unrendered blocks 885 RenderBlock* block = toRenderBlock(renderer); 886 if (block->height() == 0 || (block->isListItem() && block->isEmpty())) 887 return appendBlockPlaceholder(container); 888 889 return 0; 890} 891 892// Assumes that the position is at a placeholder and does the removal without much checking. 893void CompositeEditCommand::removePlaceholderAt(const Position& p) 894{ 895 ASSERT(lineBreakExistsAtPosition(p)); 896 897 // We are certain that the position is at a line break, but it may be a br or a preserved newline. 898 if (p.anchorNode()->hasTagName(brTag)) { 899 removeNode(p.anchorNode()); 900 return; 901 } 902 903 deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1); 904} 905 906PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) 907{ 908 RefPtr<Element> paragraphElement = createDefaultParagraphElement(document()); 909 paragraphElement->appendChild(createBreakElement(document()), IGNORE_EXCEPTION); 910 insertNodeAt(paragraphElement, position); 911 return paragraphElement.release(); 912} 913 914// If the paragraph is not entirely within it's own block, create one and move the paragraph into 915// it, and return that block. Otherwise return 0. 916PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) 917{ 918 if (pos.isNull()) 919 return 0; 920 921 document()->updateLayoutIgnorePendingStylesheets(); 922 923 // It's strange that this function is responsible for verifying that pos has not been invalidated 924 // by an earlier call to this function. The caller, applyBlockStyle, should do this. 925 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY); 926 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos)); 927 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos); 928 VisiblePosition next = visibleParagraphEnd.next(); 929 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd; 930 931 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream(); 932 Position upstreamEnd = visibleEnd.deepEquivalent().upstream(); 933 934 // If there are no VisiblePositions in the same block as pos then 935 // upstreamStart will be outside the paragraph 936 if (comparePositions(pos, upstreamStart) < 0) 937 return 0; 938 939 // Perform some checks to see if we need to perform work in this function. 940 if (isBlock(upstreamStart.deprecatedNode())) { 941 // If the block is the root editable element, always move content to a new block, 942 // since it is illegal to modify attributes on the root editable element for editing. 943 if (upstreamStart.deprecatedNode() == editableRootForPosition(upstreamStart)) { 944 // If the block is the root editable element and it contains no visible content, create a new 945 // block but don't try and move content into it, since there's nothing for moveParagraphs to move. 946 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.deprecatedNode()->renderer())) 947 return insertNewDefaultParagraphElementAt(upstreamStart); 948 } else if (isBlock(upstreamEnd.deprecatedNode())) { 949 if (!upstreamEnd.deprecatedNode()->isDescendantOf(upstreamStart.deprecatedNode())) { 950 // If the paragraph end is a descendant of paragraph start, then we need to run 951 // the rest of this function. If not, we can bail here. 952 return 0; 953 } 954 } else if (enclosingBlock(upstreamEnd.deprecatedNode()) != upstreamStart.deprecatedNode()) { 955 // The visibleEnd. It must be an ancestor of the paragraph start. 956 // We can bail as we have a full block to work with. 957 ASSERT(upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode()))); 958 return 0; 959 } else if (isEndOfEditableOrNonEditableContent(visibleEnd)) { 960 // At the end of the editable region. We can bail here as well. 961 return 0; 962 } 963 } 964 965 RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart); 966 967 bool endWasBr = visibleParagraphEnd.deepEquivalent().deprecatedNode()->hasTagName(brTag); 968 969 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.get()))); 970 971 if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr) 972 removeNode(newBlock->lastChild()); 973 974 return newBlock.release(); 975} 976 977void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode) 978{ 979 if (!anchorNode) 980 return; 981 982 ASSERT(anchorNode->isLink()); 983 984 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode)); 985 applyStyledElement(toElement(anchorNode)); 986 // Clones of anchorNode have been pushed down, now remove it. 987 if (anchorNode->inDocument()) 988 removeNodePreservingChildren(anchorNode); 989} 990 991// Clone the paragraph between start and end under blockElement, 992// preserving the hierarchy up to outerNode. 993 994void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, const Position& end, Node* passedOuterNode, Element* blockElement) 995{ 996 ASSERT(comparePositions(start, end) <= 0); 997 998 // First we clone the outerNode 999 RefPtr<Node> lastNode; 1000 RefPtr<Node> outerNode = passedOuterNode; 1001 1002 if (outerNode->isRootEditableElement()) { 1003 lastNode = blockElement; 1004 } else { 1005 lastNode = outerNode->cloneNode(isTableElement(outerNode.get())); 1006 appendNode(lastNode, blockElement); 1007 } 1008 1009 if (start.deprecatedNode() != outerNode && lastNode->isElementNode() && start.anchorNode()->isDescendantOf(outerNode.get())) { 1010 Vector<RefPtr<Node> > ancestors; 1011 1012 // Insert each node from innerNode to outerNode (excluded) in a list. 1013 for (Node* n = start.deprecatedNode(); n && n != outerNode; n = n->parentNode()) 1014 ancestors.append(n); 1015 1016 // Clone every node between start.deprecatedNode() and outerBlock. 1017 1018 for (size_t i = ancestors.size(); i != 0; --i) { 1019 Node* item = ancestors[i - 1].get(); 1020 RefPtr<Node> child = item->cloneNode(isTableElement(item)); 1021 appendNode(child, static_cast<Element *>(lastNode.get())); 1022 lastNode = child.release(); 1023 } 1024 } 1025 1026 // Handle the case of paragraphs with more than one node, 1027 // cloning all the siblings until end.deprecatedNode() is reached. 1028 1029 if (start.deprecatedNode() != end.deprecatedNode() && !start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) { 1030 // If end is not a descendant of outerNode we need to 1031 // find the first common ancestor to increase the scope 1032 // of our nextSibling traversal. 1033 while (!end.deprecatedNode()->isDescendantOf(outerNode.get())) { 1034 outerNode = outerNode->parentNode(); 1035 } 1036 1037 RefPtr<Node> startNode = start.deprecatedNode(); 1038 for (RefPtr<Node> node = NodeTraversal::nextSkippingChildren(startNode.get(), outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(node.get(), outerNode.get())) { 1039 // Move lastNode up in the tree as much as node was moved up in the 1040 // tree by NodeTraversal::nextSkippingChildren, so that the relative depth between 1041 // node and the original start node is maintained in the clone. 1042 while (startNode->parentNode() != node->parentNode()) { 1043 startNode = startNode->parentNode(); 1044 lastNode = lastNode->parentNode(); 1045 } 1046 1047 RefPtr<Node> clonedNode = node->cloneNode(true); 1048 insertNodeAfter(clonedNode, lastNode); 1049 lastNode = clonedNode.release(); 1050 if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(node.get())) 1051 break; 1052 } 1053 } 1054} 1055 1056 1057// There are bugs in deletion when it removes a fully selected table/list. 1058// It expands and removes the entire table/list, but will let content 1059// before and after the table/list collapse onto one line. 1060// Deleting a paragraph will leave a placeholder. Remove it (and prune 1061// empty or unrendered parents). 1062 1063void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination) 1064{ 1065 VisiblePosition caretAfterDelete = endingSelection().visibleStart(); 1066 if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { 1067 // Note: We want the rightmost candidate. 1068 Position position = caretAfterDelete.deepEquivalent().downstream(); 1069 Node* node = position.deprecatedNode(); 1070 // Normally deletion will leave a br as a placeholder. 1071 if (node->hasTagName(brTag)) 1072 removeNodeAndPruneAncestors(node); 1073 // If the selection to move was empty and in an empty block that 1074 // doesn't require a placeholder to prop itself open (like a bordered 1075 // div or an li), remove it during the move (the list removal code 1076 // expects this behavior). 1077 else if (isBlock(node)) { 1078 // If caret position after deletion and destination position coincides, 1079 // node should not be removed. 1080 if (!position.rendersInDifferentPosition(destination.deepEquivalent())) { 1081 prune(node); 1082 return; 1083 } 1084 removeNodeAndPruneAncestors(node); 1085 } 1086 else if (lineBreakExistsAtPosition(position)) { 1087 // There is a preserved '\n' at caretAfterDelete. 1088 // We can safely assume this is a text node. 1089 Text* textNode = toText(node); 1090 if (textNode->length() == 1) 1091 removeNodeAndPruneAncestors(node); 1092 else 1093 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); 1094 } 1095 } 1096} 1097 1098// This is a version of moveParagraph that preserves style by keeping the original markup 1099// It is currently used only by IndentOutdentCommand but it is meant to be used in the 1100// future by several other commands such as InsertList and the align commands. 1101// The blockElement parameter is the element to move the paragraph to, 1102// outerNode is the top element of the paragraph hierarchy. 1103 1104void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode) 1105{ 1106 ASSERT(outerNode); 1107 ASSERT(blockElement); 1108 1109 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); 1110 VisiblePosition afterParagraph(endOfParagraphToMove.next()); 1111 1112 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. 1113 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. 1114 Position start = startOfParagraphToMove.deepEquivalent().downstream(); 1115 Position end = startOfParagraphToMove == endOfParagraphToMove ? start : endOfParagraphToMove.deepEquivalent().upstream(); 1116 1117 cloneParagraphUnderNewElement(start, end, outerNode, blockElement); 1118 1119 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); 1120 deleteSelection(false, false, false, false); 1121 1122 // There are bugs in deletion when it removes a fully selected table/list. 1123 // It expands and removes the entire table/list, but will let content 1124 // before and after the table/list collapse onto one line. 1125 1126 cleanupAfterDeletion(); 1127 1128 // Add a br if pruning an empty block level element caused a collapse. For example: 1129 // foo^ 1130 // <div>bar</div> 1131 // baz 1132 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would 1133 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. 1134 // Must recononicalize these two VisiblePositions after the pruning above. 1135 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); 1136 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); 1137 1138 if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().deprecatedNode()) 1139 && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) { 1140 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. 1141 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); 1142 } 1143} 1144 1145 1146// This moves a paragraph preserving its style. 1147void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) 1148{ 1149 ASSERT(isStartOfParagraph(startOfParagraphToMove)); 1150 ASSERT(isEndOfParagraph(endOfParagraphToMove)); 1151 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle); 1152} 1153 1154void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) 1155{ 1156 if (startOfParagraphToMove == destination) 1157 return; 1158 1159 int startIndex = -1; 1160 int endIndex = -1; 1161 int destinationIndex = -1; 1162 bool originalIsDirectional = endingSelection().isDirectional(); 1163 if (preserveSelection && !endingSelection().isNone()) { 1164 VisiblePosition visibleStart = endingSelection().visibleStart(); 1165 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 1166 1167 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0; 1168 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0; 1169 1170 if (!startAfterParagraph && !endBeforeParagraph) { 1171 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0; 1172 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0; 1173 1174 startIndex = 0; 1175 if (startInParagraph) { 1176 RefPtr<Range> startRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleStart.deepEquivalent().parentAnchoredEquivalent()); 1177 startIndex = TextIterator::rangeLength(startRange.get(), true); 1178 } 1179 1180 endIndex = 0; 1181 if (endInParagraph) { 1182 RefPtr<Range> endRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleEnd.deepEquivalent().parentAnchoredEquivalent()); 1183 endIndex = TextIterator::rangeLength(endRange.get(), true); 1184 } 1185 } 1186 } 1187 1188 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(CannotCrossEditingBoundary); 1189 VisiblePosition afterParagraph(endOfParagraphToMove.next(CannotCrossEditingBoundary)); 1190 1191 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. 1192 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. 1193 Position start = startOfParagraphToMove.deepEquivalent().downstream(); 1194 Position end = endOfParagraphToMove.deepEquivalent().upstream(); 1195 1196 // start and end can't be used directly to create a Range; they are "editing positions" 1197 Position startRangeCompliant = start.parentAnchoredEquivalent(); 1198 Position endRangeCompliant = end.parentAnchoredEquivalent(); 1199 RefPtr<Range> range = Range::create(document(), startRangeCompliant.deprecatedNode(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.deprecatedNode(), endRangeCompliant.deprecatedEditingOffset()); 1200 1201 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It 1202 // shouldn't matter though, since moved paragraphs will usually be quite small. 1203 RefPtr<DocumentFragment> fragment; 1204 // This used to use a ternary for initialization, but that confused some versions of GCC, see bug 37912 1205 if (startOfParagraphToMove != endOfParagraphToMove) 1206 fragment = createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), ""); 1207 1208 // A non-empty paragraph's style is moved when we copy and move it. We don't move 1209 // anything if we're given an empty paragraph, but an empty paragraph can have style 1210 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later. 1211 RefPtr<EditingStyle> styleInEmptyParagraph; 1212 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) { 1213 styleInEmptyParagraph = EditingStyle::create(startOfParagraphToMove.deepEquivalent()); 1214 styleInEmptyParagraph->mergeTypingStyle(document()); 1215 // The moved paragraph should assume the block style of the destination. 1216 styleInEmptyParagraph->removeBlockProperties(); 1217 } 1218 1219 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here. 1220 1221 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); 1222 document()->frame()->editor().clearMisspellingsAndBadGrammar(endingSelection()); 1223 deleteSelection(false, false, false, false); 1224 1225 ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); 1226 cleanupAfterDeletion(destination); 1227 ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); 1228 1229 // Add a br if pruning an empty block level element caused a collapse. For example: 1230 // foo^ 1231 // <div>bar</div> 1232 // baz 1233 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would 1234 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. 1235 // Must recononicalize these two VisiblePositions after the pruning above. 1236 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); 1237 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); 1238 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { 1239 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. 1240 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); 1241 // Need an updateLayout here in case inserting the br has split a text node. 1242 document()->updateLayoutIgnorePendingStylesheets(); 1243 } 1244 1245 RefPtr<Range> startToDestinationRange(Range::create(document(), firstPositionInNode(document()->documentElement()), destination.deepEquivalent().parentAnchoredEquivalent())); 1246 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true); 1247 1248 setEndingSelection(VisibleSelection(destination, originalIsDirectional)); 1249 ASSERT(endingSelection().isCaretOrRange()); 1250 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MovingParagraph; 1251 if (!preserveStyle) 1252 options |= ReplaceSelectionCommand::MatchStyle; 1253 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, options)); 1254 1255 document()->frame()->editor().markMisspellingsAndBadGrammar(endingSelection()); 1256 1257 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph. 1258 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart()); 1259 if (styleInEmptyParagraph && selectionIsEmptyParagraph) 1260 applyStyle(styleInEmptyParagraph.get()); 1261 1262 if (preserveSelection && startIndex != -1) { 1263 // Fragment creation (using createMarkup) incorrectly uses regular 1264 // spaces instead of nbsps for some spaces that were rendered (11475), which 1265 // causes spaces to be collapsed during the move operation. This results 1266 // in a call to rangeFromLocationAndLength with a location past the end 1267 // of the document (which will return null). 1268 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true); 1269 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true); 1270 if (start && end) 1271 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM, originalIsDirectional)); 1272 } 1273} 1274 1275// FIXME: Send an appropriate shouldDeleteRange call. 1276bool CompositeEditCommand::breakOutOfEmptyListItem() 1277{ 1278 RefPtr<Node> emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); 1279 if (!emptyListItem) 1280 return false; 1281 1282 RefPtr<EditingStyle> style = EditingStyle::create(endingSelection().start()); 1283 style->mergeTypingStyle(document()); 1284 1285 RefPtr<ContainerNode> listNode = emptyListItem->parentNode(); 1286 // FIXME: Can't we do something better when the immediate parent wasn't a list node? 1287 if (!listNode 1288 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag)) 1289 || !listNode->rendererIsEditable() 1290 || listNode == emptyListItem->rootEditableElement()) 1291 return false; 1292 1293 RefPtr<Element> newBlock = 0; 1294 if (ContainerNode* blockEnclosingList = listNode->parentNode()) { 1295 if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item 1296 if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode.get())) { 1297 // If listNode appears at the end of the outer list item, then move listNode outside of this list item 1298 // 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 1299 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph. 1300 // 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 1301 splitElement(toElement(blockEnclosingList), listNode); 1302 removeNodePreservingChildren(listNode->parentNode()); 1303 newBlock = createListItemElement(document()); 1304 } 1305 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. 1306 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) 1307 newBlock = createListItemElement(document()); 1308 } 1309 if (!newBlock) 1310 newBlock = createDefaultParagraphElement(document()); 1311 1312 RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? toElement(emptyListItem.get())->previousElementSibling(): emptyListItem->previousSibling(); 1313 RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? toElement(emptyListItem.get())->nextElementSibling(): emptyListItem->nextSibling(); 1314 if (isListItem(nextListNode.get()) || isListElement(nextListNode.get())) { 1315 // If emptyListItem follows another list item or nested list, split the list node. 1316 if (isListItem(previousListNode.get()) || isListElement(previousListNode.get())) 1317 splitElement(toElement(listNode.get()), emptyListItem); 1318 1319 // If emptyListItem is followed by other list item or nested list, then insert newBlock before the list node. 1320 // Because we have splitted the element, emptyListItem is the first element in the list node. 1321 // i.e. insert newBlock before ul or ol whose first element is emptyListItem 1322 insertNodeBefore(newBlock, listNode); 1323 removeNode(emptyListItem); 1324 } else { 1325 // When emptyListItem does not follow any list item or nested list, insert newBlock after the enclosing list node. 1326 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem. 1327 insertNodeAfter(newBlock, listNode); 1328 removeNode(isListItem(previousListNode.get()) || isListElement(previousListNode.get()) ? emptyListItem.get() : listNode.get()); 1329 } 1330 1331 appendBlockPlaceholder(newBlock); 1332 setEndingSelection(VisibleSelection(firstPositionInNode(newBlock.get()), DOWNSTREAM, endingSelection().isDirectional())); 1333 1334 style->prepareToApplyAt(endingSelection().start()); 1335 if (!style->isEmpty()) 1336 applyStyle(style.get()); 1337 1338 return true; 1339} 1340 1341// If the caret is in an empty quoted paragraph, and either there is nothing before that 1342// paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph. 1343bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() 1344{ 1345 if (!endingSelection().isCaret()) 1346 return false; 1347 1348 VisiblePosition caret(endingSelection().visibleStart()); 1349 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote); 1350 if (!highestBlockquote) 1351 return false; 1352 1353 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret)) 1354 return false; 1355 1356 VisiblePosition previous(caret.previous(CannotCrossEditingBoundary)); 1357 // Only move forward if there's nothing before the caret, or if there's unquoted content before it. 1358 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote)) 1359 return false; 1360 1361 RefPtr<Node> br = createBreakElement(document()); 1362 // We want to replace this quoted paragraph with an unquoted one, so insert a br 1363 // to hold the caret before the highest blockquote. 1364 insertNodeBefore(br, highestBlockquote); 1365 VisiblePosition atBR(positionBeforeNode(br.get())); 1366 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert 1367 // a second one. 1368 if (!isStartOfParagraph(atBR)) 1369 insertNodeBefore(createBreakElement(document()), br); 1370 setEndingSelection(VisibleSelection(atBR, endingSelection().isDirectional())); 1371 1372 // If this is an empty paragraph there must be a line break here. 1373 if (!lineBreakExistsAtVisiblePosition(caret)) 1374 return false; 1375 1376 Position caretPos(caret.deepEquivalent().downstream()); 1377 // A line break is either a br or a preserved newline. 1378 ASSERT(caretPos.deprecatedNode()->hasTagName(brTag) || (caretPos.deprecatedNode()->isTextNode() && caretPos.deprecatedNode()->renderer()->style()->preserveNewline())); 1379 1380 if (caretPos.deprecatedNode()->hasTagName(brTag)) 1381 removeNodeAndPruneAncestors(caretPos.deprecatedNode()); 1382 else if (caretPos.deprecatedNode()->isTextNode()) { 1383 ASSERT(caretPos.deprecatedEditingOffset() == 0); 1384 Text* textNode = toText(caretPos.deprecatedNode()); 1385 ContainerNode* parentNode = textNode->parentNode(); 1386 // The preserved newline must be the first thing in the node, since otherwise the previous 1387 // paragraph would be quoted, and we verified that it wasn't above. 1388 deleteTextFromNode(textNode, 0, 1); 1389 prune(parentNode); 1390 } 1391 1392 return true; 1393} 1394 1395// Operations use this function to avoid inserting content into an anchor when at the start or the end of 1396// that anchor, as in NSTextView. 1397// FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how 1398// the caret was made. 1399Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original) 1400{ 1401 if (original.isNull()) 1402 return original; 1403 1404 VisiblePosition visiblePos(original); 1405 Node* enclosingAnchor = enclosingAnchorElement(original); 1406 Position result = original; 1407 1408 if (!enclosingAnchor) 1409 return result; 1410 1411 // Don't avoid block level anchors, because that would insert content into the wrong paragraph. 1412 if (enclosingAnchor && !isBlock(enclosingAnchor)) { 1413 VisiblePosition firstInAnchor(firstPositionInNode(enclosingAnchor)); 1414 VisiblePosition lastInAnchor(lastPositionInNode(enclosingAnchor)); 1415 // If visually just after the anchor, insert *inside* the anchor unless it's the last 1416 // VisiblePosition in the document, to match NSTextView. 1417 if (visiblePos == lastInAnchor) { 1418 // Make sure anchors are pushed down before avoiding them so that we don't 1419 // also avoid structural elements like lists and blocks (5142012). 1420 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) { 1421 pushAnchorElementDown(enclosingAnchor); 1422 enclosingAnchor = enclosingAnchorElement(original); 1423 if (!enclosingAnchor) 1424 return original; 1425 } 1426 // Don't insert outside an anchor if doing so would skip over a line break. It would 1427 // probably be safe to move the line break so that we could still avoid the anchor here. 1428 Position downstream(visiblePos.deepEquivalent().downstream()); 1429 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.deprecatedNode()->isDescendantOf(enclosingAnchor)) 1430 return original; 1431 1432 result = positionInParentAfterNode(enclosingAnchor); 1433 } 1434 // If visually just before an anchor, insert *outside* the anchor unless it's the first 1435 // VisiblePosition in a paragraph, to match NSTextView. 1436 if (visiblePos == firstInAnchor) { 1437 // Make sure anchors are pushed down before avoiding them so that we don't 1438 // also avoid structural elements like lists and blocks (5142012). 1439 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) { 1440 pushAnchorElementDown(enclosingAnchor); 1441 enclosingAnchor = enclosingAnchorElement(original); 1442 } 1443 if (!enclosingAnchor) 1444 return original; 1445 1446 result = positionInParentBeforeNode(enclosingAnchor); 1447 } 1448 } 1449 1450 if (result.isNull() || !editableRootForPosition(result)) 1451 result = original; 1452 1453 return result; 1454} 1455 1456// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions 1457// to determine if the split is necessary. Returns the last split node. 1458PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor) 1459{ 1460 ASSERT(start); 1461 ASSERT(end); 1462 ASSERT(start != end); 1463 1464 RefPtr<Node> node; 1465 if (shouldSplitAncestor && end->parentNode()) 1466 end = end->parentNode(); 1467 1468 RefPtr<Node> endNode = end; 1469 for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) { 1470 if (!node->parentNode()->isElementNode()) 1471 break; 1472 // Do not split a node when doing so introduces an empty node. 1473 VisiblePosition positionInParent = firstPositionInNode(node->parentNode()); 1474 VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get()); 1475 if (positionInParent != positionInNode) 1476 splitElement(toElement(node->parentNode()), node); 1477 } 1478 1479 return node.release(); 1480} 1481 1482PassRefPtr<Element> createBlockPlaceholderElement(Document* document) 1483{ 1484 RefPtr<Element> breakNode = document->createElement(brTag, false); 1485 return breakNode.release(); 1486} 1487 1488} // namespace WebCore 1489