CompositeEditCommand.cpp revision ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb
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 "CharacterNames.h" 32#include "DeleteFromTextNodeCommand.h" 33#include "DeleteSelectionCommand.h" 34#include "Document.h" 35#include "DocumentFragment.h" 36#include "EditorInsertAction.h" 37#include "Frame.h" 38#include "HTMLElement.h" 39#include "HTMLNames.h" 40#include "InlineTextBox.h" 41#include "InsertIntoTextNodeCommand.h" 42#include "InsertLineBreakCommand.h" 43#include "InsertNodeBeforeCommand.h" 44#include "InsertParagraphSeparatorCommand.h" 45#include "InsertTextCommand.h" 46#include "JoinTextNodesCommand.h" 47#include "MergeIdenticalElementsCommand.h" 48#include "Range.h" 49#include "RemoveCSSPropertyCommand.h" 50#include "RemoveNodeCommand.h" 51#include "RemoveNodePreservingChildrenCommand.h" 52#include "ReplaceNodeWithSpanCommand.h" 53#include "ReplaceSelectionCommand.h" 54#include "RenderBlock.h" 55#include "RenderText.h" 56#include "SetNodeAttributeCommand.h" 57#include "SplitElementCommand.h" 58#include "SplitTextNodeCommand.h" 59#include "SplitTextNodeContainingElementCommand.h" 60#include "Text.h" 61#include "TextIterator.h" 62#include "WrapContentsInDummySpanCommand.h" 63#include "htmlediting.h" 64#include "markup.h" 65#include "visible_units.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 Element* parent = refChild->parentElement(); 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.node(); 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<Element> 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.node())) 346 return pos; 347 348 Node* tabSpan = tabSpanNode(pos.node()); 349 350 if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.node())) 351 return positionInParentBeforeNode(tabSpan); 352 353 if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node())) 354 return positionInParentAfterNode(tabSpan); 355 356 splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.deprecatedEditingOffset()); 357 return positionInParentBeforeNode(tabSpan); 358} 359 360void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, const Position& pos) 361{ 362 // insert node before, after, or at split of tab span 363 insertNodeAt(node, positionOutsideTabSpan(pos)); 364} 365 366void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) 367{ 368 if (endingSelection().isRange()) 369 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); 370} 371 372void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) 373{ 374 if (selection.isRange()) 375 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); 376} 377 378void CompositeEditCommand::removeCSSProperty(PassRefPtr<StyledElement> element, CSSPropertyID property) 379{ 380 applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), element, property)); 381} 382 383void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute) 384{ 385 setNodeAttribute(element, attribute, AtomicString()); 386} 387 388void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value) 389{ 390 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value)); 391} 392 393static inline bool isWhitespace(UChar c) 394{ 395 return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t'; 396} 397 398// FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc). 399void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) 400{ 401 Node* node = position.containerNode(); 402 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode()) 403 return; 404 Text* textNode = static_cast<Text*>(node); 405 406 if (textNode->length() == 0) 407 return; 408 RenderObject* renderer = textNode->renderer(); 409 if (renderer && !renderer->style()->collapseWhiteSpace()) 410 return; 411 412 String text = textNode->data(); 413 ASSERT(!text.isEmpty()); 414 415 int offset = position.deprecatedEditingOffset(); 416 // If neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. 417 if (!isWhitespace(text[offset])) { 418 offset--; 419 if (offset < 0 || !isWhitespace(text[offset])) 420 return; 421 } 422 423 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset]. 424 int upstream = offset; 425 while (upstream > 0 && isWhitespace(text[upstream - 1])) 426 upstream--; 427 428 int downstream = offset; 429 while ((unsigned)downstream + 1 < text.length() && isWhitespace(text[downstream + 1])) 430 downstream++; 431 432 int length = downstream - upstream + 1; 433 ASSERT(length > 0); 434 435 VisiblePosition visibleUpstreamPos(Position(position.containerNode(), upstream, Position::PositionIsOffsetInAnchor)); 436 VisiblePosition visibleDownstreamPos(Position(position.containerNode(), downstream + 1, Position::PositionIsOffsetInAnchor)); 437 438 String string = text.substring(upstream, length); 439 String rebalancedString = stringWithRebalancedWhitespace(string, 440 // 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 441 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node. 442 isStartOfParagraph(visibleUpstreamPos) || upstream == 0, 443 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length() - 1); 444 445 if (string != rebalancedString) 446 replaceTextInNode(textNode, upstream, length, rebalancedString); 447} 448 449void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position) 450{ 451 Node* node = position.node(); 452 if (!node || !node->isTextNode()) 453 return; 454 Text* textNode = static_cast<Text*>(node); 455 456 if (textNode->length() == 0) 457 return; 458 RenderObject* renderer = textNode->renderer(); 459 if (renderer && !renderer->style()->collapseWhiteSpace()) 460 return; 461 462 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it. 463 Position upstreamPos = position.upstream(); 464 deleteInsignificantText(position.upstream(), position.downstream()); 465 position = upstreamPos.downstream(); 466 467 VisiblePosition visiblePos(position); 468 VisiblePosition previousVisiblePos(visiblePos.previous()); 469 Position previous(previousVisiblePos.deepEquivalent()); 470 471 if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.node()->isTextNode() && !previous.node()->hasTagName(brTag)) 472 replaceTextInNode(static_cast<Text*>(previous.node()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 473 if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.node()->isTextNode() && !position.node()->hasTagName(brTag)) 474 replaceTextInNode(static_cast<Text*>(position.node()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 475} 476 477void CompositeEditCommand::rebalanceWhitespace() 478{ 479 VisibleSelection selection = endingSelection(); 480 if (selection.isNone()) 481 return; 482 483 rebalanceWhitespaceAt(selection.start()); 484 if (selection.isRange()) 485 rebalanceWhitespaceAt(selection.end()); 486} 487 488void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, unsigned start, unsigned end) 489{ 490 if (!textNode || start >= end) 491 return; 492 493 RenderText* textRenderer = toRenderText(textNode->renderer()); 494 if (!textRenderer) 495 return; 496 497 Vector<InlineTextBox*> sortedTextBoxes; 498 size_t sortedTextBoxesPosition = 0; 499 500 for (InlineTextBox* textBox = textRenderer->firstTextBox(); textBox; textBox = textBox->nextTextBox()) 501 sortedTextBoxes.append(textBox); 502 503 // If there is mixed directionality text, the boxes can be out of order, 504 // (like Arabic with embedded LTR), so sort them first. 505 if (textRenderer->containsReversedText()) 506 std::sort(sortedTextBoxes.begin(), sortedTextBoxes.end(), InlineTextBox::compareByStart); 507 InlineTextBox* box = sortedTextBoxes.isEmpty() ? 0 : sortedTextBoxes[sortedTextBoxesPosition]; 508 509 if (!box) { 510 // whole text node is empty 511 removeNode(textNode); 512 return; 513 } 514 515 unsigned length = textNode->length(); 516 if (start >= length || end > length) 517 return; 518 519 unsigned removed = 0; 520 InlineTextBox* prevBox = 0; 521 String str; 522 523 // This loop structure works to process all gaps preceding a box, 524 // and also will look at the gap after the last box. 525 while (prevBox || box) { 526 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0; 527 if (end < gapStart) 528 // No more chance for any intersections 529 break; 530 531 unsigned gapEnd = box ? box->start() : length; 532 bool indicesIntersect = start <= gapEnd && end >= gapStart; 533 int gapLen = gapEnd - gapStart; 534 if (indicesIntersect && gapLen > 0) { 535 gapStart = max(gapStart, start); 536 gapEnd = min(gapEnd, end); 537 if (str.isNull()) 538 str = textNode->data().substring(start, end - start); 539 // remove text in the gap 540 str.remove(gapStart - start - removed, gapLen); 541 removed += gapLen; 542 } 543 544 prevBox = box; 545 if (box) { 546 if (++sortedTextBoxesPosition < sortedTextBoxes.size()) 547 box = sortedTextBoxes[sortedTextBoxesPosition]; 548 else 549 box = 0; 550 } 551 } 552 553 if (!str.isNull()) { 554 // Replace the text between start and end with our pruned version. 555 if (!str.isEmpty()) 556 replaceTextInNode(textNode, start, end - start, str); 557 else { 558 // Assert that we are not going to delete all of the text in the node. 559 // If we were, that should have been done above with the call to 560 // removeNode and return. 561 ASSERT(start > 0 || end - start < textNode->length()); 562 deleteTextFromNode(textNode, start, end - start); 563 } 564 } 565} 566 567void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end) 568{ 569 if (start.isNull() || end.isNull()) 570 return; 571 572 if (comparePositions(start, end) >= 0) 573 return; 574 575 Node* next; 576 for (Node* node = start.node(); node; node = next) { 577 next = node->traverseNextNode(); 578 if (node->isTextNode()) { 579 Text* textNode = static_cast<Text*>(node); 580 int startOffset = node == start.node() ? start.deprecatedEditingOffset() : 0; 581 int endOffset = node == end.node() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length()); 582 deleteInsignificantText(textNode, startOffset, endOffset); 583 } 584 if (node == end.node()) 585 break; 586 } 587} 588 589void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos) 590{ 591 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(); 592 deleteInsignificantText(pos, end); 593} 594 595PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container) 596{ 597 if (!container) 598 return 0; 599 600 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. 601 ASSERT(container->renderer()); 602 603 RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); 604 appendNode(placeholder, container); 605 return placeholder.release(); 606} 607 608PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos) 609{ 610 if (pos.isNull()) 611 return 0; 612 613 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964. 614 ASSERT(pos.node()->renderer()); 615 616 RefPtr<Node> placeholder = createBlockPlaceholderElement(document()); 617 insertNodeAt(placeholder, pos); 618 return placeholder.release(); 619} 620 621PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container) 622{ 623 if (!container) 624 return 0; 625 626 updateLayout(); 627 628 RenderObject* renderer = container->renderer(); 629 if (!renderer || !renderer->isBlockFlow()) 630 return 0; 631 632 // append the placeholder to make sure it follows 633 // any unrendered blocks 634 RenderBlock* block = toRenderBlock(renderer); 635 if (block->height() == 0 || (block->isListItem() && block->isEmpty())) 636 return appendBlockPlaceholder(container); 637 638 return 0; 639} 640 641// Assumes that the position is at a placeholder and does the removal without much checking. 642void CompositeEditCommand::removePlaceholderAt(const Position& p) 643{ 644 ASSERT(lineBreakExistsAtPosition(p)); 645 646 // We are certain that the position is at a line break, but it may be a br or a preserved newline. 647 if (p.anchorNode()->hasTagName(brTag)) { 648 removeNode(p.anchorNode()); 649 return; 650 } 651 652 deleteTextFromNode(static_cast<Text*>(p.anchorNode()), p.offsetInContainerNode(), 1); 653} 654 655PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position) 656{ 657 RefPtr<Element> paragraphElement = createDefaultParagraphElement(document()); 658 ExceptionCode ec; 659 paragraphElement->appendChild(createBreakElement(document()), ec); 660 insertNodeAt(paragraphElement, position); 661 return paragraphElement.release(); 662} 663 664// If the paragraph is not entirely within it's own block, create one and move the paragraph into 665// it, and return that block. Otherwise return 0. 666PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos) 667{ 668 if (pos.isNull()) 669 return 0; 670 671 updateLayout(); 672 673 // It's strange that this function is responsible for verifying that pos has not been invalidated 674 // by an earlier call to this function. The caller, applyBlockStyle, should do this. 675 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY); 676 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos)); 677 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos); 678 VisiblePosition next = visibleParagraphEnd.next(); 679 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd; 680 681 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream(); 682 Position upstreamEnd = visibleEnd.deepEquivalent().upstream(); 683 684 // If there are no VisiblePositions in the same block as pos then 685 // upstreamStart will be outside the paragraph 686 if (comparePositions(pos, upstreamStart) < 0) 687 return 0; 688 689 // Perform some checks to see if we need to perform work in this function. 690 if (isBlock(upstreamStart.node())) { 691 // If the block is the root editable element, always move content to a new block, 692 // since it is illegal to modify attributes on the root editable element for editing. 693 if (upstreamStart.node() == editableRootForPosition(upstreamStart)) { 694 // If the block is the root editable element and it contains no visible content, create a new 695 // block but don't try and move content into it, since there's nothing for moveParagraphs to move. 696 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.node()->renderer())) 697 return insertNewDefaultParagraphElementAt(upstreamStart); 698 } else if (isBlock(upstreamEnd.node())) { 699 if (!upstreamEnd.node()->isDescendantOf(upstreamStart.node())) { 700 // If the paragraph end is a descendant of paragraph start, then we need to run 701 // the rest of this function. If not, we can bail here. 702 return 0; 703 } 704 } 705 else if (enclosingBlock(upstreamEnd.node()) != upstreamStart.node()) { 706 // The visibleEnd. It must be an ancestor of the paragraph start. 707 // We can bail as we have a full block to work with. 708 ASSERT(upstreamStart.node()->isDescendantOf(enclosingBlock(upstreamEnd.node()))); 709 return 0; 710 } 711 else if (isEndOfDocument(visibleEnd)) { 712 // At the end of the document. We can bail here as well. 713 return 0; 714 } 715 } 716 717 RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart); 718 719 bool endWasBr = visibleParagraphEnd.deepEquivalent().node()->hasTagName(brTag); 720 721 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.get()))); 722 723 if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr) 724 removeNode(newBlock->lastChild()); 725 726 return newBlock.release(); 727} 728 729void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode) 730{ 731 if (!anchorNode) 732 return; 733 734 ASSERT(anchorNode->isLink()); 735 736 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode)); 737 applyStyledElement(static_cast<Element*>(anchorNode)); 738 // Clones of anchorNode have been pushed down, now remove it. 739 if (anchorNode->inDocument()) 740 removeNodePreservingChildren(anchorNode); 741} 742 743// Clone the paragraph between start and end under blockElement, 744// preserving the hierarchy up to outerNode. 745 746void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement) 747{ 748 // First we clone the outerNode 749 750 RefPtr<Node> topNode = outerNode->cloneNode(isTableElement(outerNode)); 751 appendNode(topNode, blockElement); 752 RefPtr<Node> lastNode = topNode; 753 754 if (start.node() != outerNode) { 755 Vector<RefPtr<Node> > ancestors; 756 757 // Insert each node from innerNode to outerNode (excluded) in a list. 758 for (Node* n = start.node(); n && n != outerNode; n = n->parentNode()) 759 ancestors.append(n); 760 761 // Clone every node between start.node() and outerBlock. 762 763 for (size_t i = ancestors.size(); i != 0; --i) { 764 Node* item = ancestors[i - 1].get(); 765 RefPtr<Node> child = item->cloneNode(isTableElement(item)); 766 appendNode(child, static_cast<Element *>(lastNode.get())); 767 lastNode = child.release(); 768 } 769 } 770 771 // Handle the case of paragraphs with more than one node, 772 // cloning all the siblings until end.node() is reached. 773 774 if (start.node() != end.node() && !start.node()->isDescendantOf(end.node())) { 775 // If end is not a descendant of outerNode we need to 776 // find the first common ancestor and adjust the insertion 777 // point accordingly. 778 while (!end.node()->isDescendantOf(outerNode)) { 779 outerNode = outerNode->parentNode(); 780 topNode = topNode->parentNode(); 781 } 782 783 for (Node* n = start.node()->traverseNextSibling(outerNode); n; n = n->traverseNextSibling(outerNode)) { 784 if (n->parentNode() != start.node()->parentNode()) 785 lastNode = topNode->lastChild(); 786 787 RefPtr<Node> clonedNode = n->cloneNode(true); 788 insertNodeAfter(clonedNode, lastNode); 789 lastNode = clonedNode.release(); 790 if (n == end.node() || end.node()->isDescendantOf(n)) 791 break; 792 } 793 } 794} 795 796 797// There are bugs in deletion when it removes a fully selected table/list. 798// It expands and removes the entire table/list, but will let content 799// before and after the table/list collapse onto one line. 800// Deleting a paragraph will leave a placeholder. Remove it (and prune 801// empty or unrendered parents). 802 803void CompositeEditCommand::cleanupAfterDeletion() 804{ 805 VisiblePosition caretAfterDelete = endingSelection().visibleStart(); 806 if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { 807 // Note: We want the rightmost candidate. 808 Position position = caretAfterDelete.deepEquivalent().downstream(); 809 Node* node = position.node(); 810 // Normally deletion will leave a br as a placeholder. 811 if (node->hasTagName(brTag)) 812 removeNodeAndPruneAncestors(node); 813 // If the selection to move was empty and in an empty block that 814 // doesn't require a placeholder to prop itself open (like a bordered 815 // div or an li), remove it during the move (the list removal code 816 // expects this behavior). 817 else if (isBlock(node)) 818 removeNodeAndPruneAncestors(node); 819 else if (lineBreakExistsAtPosition(position)) { 820 // There is a preserved '\n' at caretAfterDelete. 821 // We can safely assume this is a text node. 822 Text* textNode = static_cast<Text*>(node); 823 if (textNode->length() == 1) 824 removeNodeAndPruneAncestors(node); 825 else 826 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1); 827 } 828 } 829} 830 831// This is a version of moveParagraph that preserves style by keeping the original markup 832// It is currently used only by IndentOutdentCommand but it is meant to be used in the 833// future by several other commands such as InsertList and the align commands. 834// The blockElement parameter is the element to move the paragraph to, 835// outerNode is the top element of the paragraph hierarchy. 836 837void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode) 838{ 839 ASSERT(outerNode); 840 ASSERT(blockElement); 841 842 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); 843 VisiblePosition afterParagraph(endOfParagraphToMove.next()); 844 845 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. 846 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. 847 Position start = startOfParagraphToMove.deepEquivalent().downstream(); 848 Position end = endOfParagraphToMove.deepEquivalent().upstream(); 849 850 cloneParagraphUnderNewElement(start, end, outerNode, blockElement); 851 852 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); 853 deleteSelection(false, false, false, false); 854 855 // There are bugs in deletion when it removes a fully selected table/list. 856 // It expands and removes the entire table/list, but will let content 857 // before and after the table/list collapse onto one line. 858 859 cleanupAfterDeletion(); 860 861 // Add a br if pruning an empty block level element caused a collapse. For example: 862 // foo^ 863 // <div>bar</div> 864 // baz 865 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would 866 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. 867 // Must recononicalize these two VisiblePositions after the pruning above. 868 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); 869 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); 870 871 if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) 872 && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) { 873 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. 874 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); 875 } 876} 877 878 879// This moves a paragraph preserving its style. 880void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) 881{ 882 ASSERT(isStartOfParagraph(startOfParagraphToMove)); 883 ASSERT(isEndOfParagraph(endOfParagraphToMove)); 884 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle); 885} 886 887void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle) 888{ 889 if (startOfParagraphToMove == destination) 890 return; 891 892 int startIndex = -1; 893 int endIndex = -1; 894 int destinationIndex = -1; 895 if (preserveSelection && !endingSelection().isNone()) { 896 VisiblePosition visibleStart = endingSelection().visibleStart(); 897 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 898 899 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0; 900 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0; 901 902 if (!startAfterParagraph && !endBeforeParagraph) { 903 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0; 904 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0; 905 906 startIndex = 0; 907 if (startInParagraph) { 908 RefPtr<Range> startRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleStart.deepEquivalent().parentAnchoredEquivalent()); 909 startIndex = TextIterator::rangeLength(startRange.get(), true); 910 } 911 912 endIndex = 0; 913 if (endInParagraph) { 914 RefPtr<Range> endRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleEnd.deepEquivalent().parentAnchoredEquivalent()); 915 endIndex = TextIterator::rangeLength(endRange.get(), true); 916 } 917 } 918 } 919 920 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); 921 VisiblePosition afterParagraph(endOfParagraphToMove.next()); 922 923 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. 924 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. 925 Position start = startOfParagraphToMove.deepEquivalent().downstream(); 926 Position end = endOfParagraphToMove.deepEquivalent().upstream(); 927 928 // start and end can't be used directly to create a Range; they are "editing positions" 929 Position startRangeCompliant = start.parentAnchoredEquivalent(); 930 Position endRangeCompliant = end.parentAnchoredEquivalent(); 931 RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset()); 932 933 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It 934 // shouldn't matter though, since moved paragraphs will usually be quite small. 935 RefPtr<DocumentFragment> fragment; 936 // This used to use a ternary for initialization, but that confused some versions of GCC, see bug 37912 937 if (startOfParagraphToMove != endOfParagraphToMove) 938 fragment = createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), ""); 939 940 // A non-empty paragraph's style is moved when we copy and move it. We don't move 941 // anything if we're given an empty paragraph, but an empty paragraph can have style 942 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later. 943 RefPtr<EditingStyle> styleInEmptyParagraph; 944 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) { 945 styleInEmptyParagraph = editingStyleIncludingTypingStyle(startOfParagraphToMove.deepEquivalent()); 946 // The moved paragraph should assume the block style of the destination. 947 styleInEmptyParagraph->removeBlockProperties(); 948 } 949 950 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here. 951 952 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); 953 document()->frame()->editor()->clearMisspellingsAndBadGrammar(endingSelection()); 954 deleteSelection(false, false, false, false); 955 956 ASSERT(destination.deepEquivalent().node()->inDocument()); 957 958 cleanupAfterDeletion(); 959 ASSERT(destination.deepEquivalent().node()->inDocument()); 960 961 // Add a br if pruning an empty block level element caused a collapse. For example: 962 // foo^ 963 // <div>bar</div> 964 // baz 965 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would 966 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. 967 // Must recononicalize these two VisiblePositions after the pruning above. 968 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); 969 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); 970 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { 971 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. 972 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); 973 // Need an updateLayout here in case inserting the br has split a text node. 974 updateLayout(); 975 } 976 977 RefPtr<Range> startToDestinationRange(Range::create(document(), firstPositionInNode(document()->documentElement()), destination.deepEquivalent().parentAnchoredEquivalent())); 978 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true); 979 980 setEndingSelection(destination); 981 ASSERT(endingSelection().isCaretOrRange()); 982 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, true, false, !preserveStyle, false, true)); 983 984 document()->frame()->editor()->markMisspellingsAndBadGrammar(endingSelection()); 985 986 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph. 987 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart()); 988 if (styleInEmptyParagraph && selectionIsEmptyParagraph) 989 applyStyle(styleInEmptyParagraph.get()); 990 991 if (preserveSelection && startIndex != -1) { 992 // Fragment creation (using createMarkup) incorrectly uses regular 993 // spaces instead of nbsps for some spaces that were rendered (11475), which 994 // causes spaces to be collapsed during the move operation. This results 995 // in a call to rangeFromLocationAndLength with a location past the end 996 // of the document (which will return null). 997 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true); 998 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true); 999 if (start && end) 1000 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM)); 1001 } 1002} 1003 1004// FIXME: Send an appropriate shouldDeleteRange call. 1005bool CompositeEditCommand::breakOutOfEmptyListItem() 1006{ 1007 Node* emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); 1008 if (!emptyListItem) 1009 return false; 1010 1011 RefPtr<EditingStyle> style = editingStyleIncludingTypingStyle(endingSelection().start()); 1012 1013 ContainerNode* listNode = emptyListItem->parentNode(); 1014 // FIXME: Can't we do something better when the immediate parent wasn't a list node? 1015 if (!listNode 1016 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag)) 1017 || !listNode->isContentEditable() 1018 || listNode == emptyListItem->rootEditableElement()) 1019 return false; 1020 1021 RefPtr<Element> newBlock = 0; 1022 if (ContainerNode* blockEnclosingList = listNode->parentNode()) { 1023 if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item 1024 if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode)) { 1025 // If listNode appears at the end of the outer list item, then move listNode outside of this list item 1026 // 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 1027 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph. 1028 // 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 1029 splitElement(static_cast<Element*>(blockEnclosingList), listNode); 1030 removeNodePreservingChildren(listNode->parentNode()); 1031 newBlock = createListItemElement(document()); 1032 } 1033 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph. 1034 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) 1035 newBlock = createListItemElement(document()); 1036 } 1037 if (!newBlock) 1038 newBlock = createDefaultParagraphElement(document()); 1039 1040 if (emptyListItem->renderer()->nextSibling()) { 1041 // If emptyListItem follows another list item, split the list node. 1042 if (emptyListItem->renderer()->previousSibling()) 1043 splitElement(static_cast<Element*>(listNode), emptyListItem); 1044 1045 // If emptyListItem is followed by other list item, then insert newBlock before the list node. 1046 // Because we have splitted the element, emptyListItem is the first element in the list node. 1047 // i.e. insert newBlock before ul or ol whose first element is emptyListItem 1048 insertNodeBefore(newBlock, listNode); 1049 removeNode(emptyListItem); 1050 } else { 1051 // When emptyListItem does not follow any list item, insert newBlock after the enclosing list node. 1052 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem. 1053 insertNodeAfter(newBlock, listNode); 1054 removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode); 1055 } 1056 1057 appendBlockPlaceholder(newBlock); 1058 setEndingSelection(VisibleSelection(firstPositionInNode(newBlock.get()), DOWNSTREAM)); 1059 1060 style->prepareToApplyAt(endingSelection().start()); 1061 if (!style->isEmpty()) 1062 applyStyle(style.get()); 1063 1064 return true; 1065} 1066 1067// If the caret is in an empty quoted paragraph, and either there is nothing before that 1068// paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph. 1069bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() 1070{ 1071 if (!endingSelection().isCaret()) 1072 return false; 1073 1074 VisiblePosition caret(endingSelection().visibleStart()); 1075 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote); 1076 if (!highestBlockquote) 1077 return false; 1078 1079 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret)) 1080 return false; 1081 1082 VisiblePosition previous(caret.previous(true)); 1083 // Only move forward if there's nothing before the caret, or if there's unquoted content before it. 1084 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote)) 1085 return false; 1086 1087 RefPtr<Node> br = createBreakElement(document()); 1088 // We want to replace this quoted paragraph with an unquoted one, so insert a br 1089 // to hold the caret before the highest blockquote. 1090 insertNodeBefore(br, highestBlockquote); 1091 VisiblePosition atBR(positionBeforeNode(br.get())); 1092 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert 1093 // a second one. 1094 if (!isStartOfParagraph(atBR)) 1095 insertNodeBefore(createBreakElement(document()), br); 1096 setEndingSelection(VisibleSelection(atBR)); 1097 1098 // If this is an empty paragraph there must be a line break here. 1099 if (!lineBreakExistsAtVisiblePosition(caret)) 1100 return false; 1101 1102 Position caretPos(caret.deepEquivalent()); 1103 // A line break is either a br or a preserved newline. 1104 ASSERT(caretPos.node()->hasTagName(brTag) || (caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline())); 1105 1106 if (caretPos.node()->hasTagName(brTag)) { 1107 Position beforeBR(positionInParentBeforeNode(caretPos.node())); 1108 removeNode(caretPos.node()); 1109 prune(beforeBR.node()); 1110 } else { 1111 ASSERT(caretPos.deprecatedEditingOffset() == 0); 1112 Text* textNode = static_cast<Text*>(caretPos.node()); 1113 ContainerNode* parentNode = textNode->parentNode(); 1114 // The preserved newline must be the first thing in the node, since otherwise the previous 1115 // paragraph would be quoted, and we verified that it wasn't above. 1116 deleteTextFromNode(textNode, 0, 1); 1117 prune(parentNode); 1118 } 1119 1120 return true; 1121} 1122 1123// Operations use this function to avoid inserting content into an anchor when at the start or the end of 1124// that anchor, as in NSTextView. 1125// FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how 1126// the caret was made. 1127Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original) 1128{ 1129 if (original.isNull()) 1130 return original; 1131 1132 VisiblePosition visiblePos(original); 1133 Node* enclosingAnchor = enclosingAnchorElement(original); 1134 Position result = original; 1135 1136 if (!enclosingAnchor) 1137 return result; 1138 1139 // Don't avoid block level anchors, because that would insert content into the wrong paragraph. 1140 if (enclosingAnchor && !isBlock(enclosingAnchor)) { 1141 VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor)); 1142 VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor)); 1143 // If visually just after the anchor, insert *inside* the anchor unless it's the last 1144 // VisiblePosition in the document, to match NSTextView. 1145 if (visiblePos == lastInAnchor) { 1146 // Make sure anchors are pushed down before avoiding them so that we don't 1147 // also avoid structural elements like lists and blocks (5142012). 1148 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) { 1149 pushAnchorElementDown(enclosingAnchor); 1150 enclosingAnchor = enclosingAnchorElement(original); 1151 if (!enclosingAnchor) 1152 return original; 1153 } 1154 // Don't insert outside an anchor if doing so would skip over a line break. It would 1155 // probably be safe to move the line break so that we could still avoid the anchor here. 1156 Position downstream(visiblePos.deepEquivalent().downstream()); 1157 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor)) 1158 return original; 1159 1160 result = positionInParentAfterNode(enclosingAnchor); 1161 } 1162 // If visually just before an anchor, insert *outside* the anchor unless it's the first 1163 // VisiblePosition in a paragraph, to match NSTextView. 1164 if (visiblePos == firstInAnchor) { 1165 // Make sure anchors are pushed down before avoiding them so that we don't 1166 // also avoid structural elements like lists and blocks (5142012). 1167 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) { 1168 pushAnchorElementDown(enclosingAnchor); 1169 enclosingAnchor = enclosingAnchorElement(original); 1170 } 1171 if (!enclosingAnchor) 1172 return original; 1173 1174 result = positionInParentBeforeNode(enclosingAnchor); 1175 } 1176 } 1177 1178 if (result.isNull() || !editableRootForPosition(result)) 1179 result = original; 1180 1181 return result; 1182} 1183 1184// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions 1185// to determine if the split is necessary. Returns the last split node. 1186PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor) 1187{ 1188 ASSERT(start != end); 1189 1190 RefPtr<Node> node; 1191 for (node = start; node && node->parentNode() != end; node = node->parentNode()) { 1192 if (!node->parentNode()->isElementNode()) 1193 break; 1194 VisiblePosition positionInParent(firstPositionInNode(node->parentNode()), DOWNSTREAM); 1195 VisiblePosition positionInNode(firstPositionInOrBeforeNode(node.get()), DOWNSTREAM); 1196 if (positionInParent != positionInNode) 1197 applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parentNode()), node)); 1198 } 1199 if (splitAncestor) { 1200 splitElement(static_cast<Element*>(end), node); 1201 return node->parentNode(); 1202 } 1203 return node.release(); 1204} 1205 1206PassRefPtr<Element> createBlockPlaceholderElement(Document* document) 1207{ 1208 RefPtr<Element> breakNode = document->createElement(brTag, false); 1209 return breakNode.release(); 1210} 1211 1212} // namespace WebCore 1213