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