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