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