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