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