1 /* 2 * Copyright (C) 2005 Apple Computer, 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/DeleteSelectionCommand.h" 28 29 #include "core/HTMLNames.h" 30 #include "core/dom/Document.h" 31 #include "core/dom/Element.h" 32 #include "core/dom/NodeTraversal.h" 33 #include "core/dom/Text.h" 34 #include "core/editing/EditingBoundary.h" 35 #include "core/editing/Editor.h" 36 #include "core/editing/VisibleUnits.h" 37 #include "core/editing/htmlediting.h" 38 #include "core/frame/LocalFrame.h" 39 #include "core/html/HTMLInputElement.h" 40 #include "core/rendering/RenderTableCell.h" 41 42 namespace WebCore { 43 44 using namespace HTMLNames; 45 46 static bool isTableCellEmpty(Node* cell) 47 { 48 ASSERT(isTableCell(cell)); 49 return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPositionInNode(cell)); 50 } 51 52 static bool isTableRowEmpty(Node* row) 53 { 54 if (!isHTMLTableRowElement(row)) 55 return false; 56 57 for (Node* child = row->firstChild(); child; child = child->nextSibling()) 58 if (isTableCell(child) && !isTableCellEmpty(child)) 59 return false; 60 61 return true; 62 } 63 64 DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool expandForSpecialElements, bool sanitizeMarkup) 65 : CompositeEditCommand(document) 66 , m_hasSelectionToDelete(false) 67 , m_smartDelete(smartDelete) 68 , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete) 69 , m_needPlaceholder(false) 70 , m_expandForSpecialElements(expandForSpecialElements) 71 , m_pruneStartBlockIfNecessary(false) 72 , m_startsAtEmptyLine(false) 73 , m_sanitizeMarkup(sanitizeMarkup) 74 , m_startBlock(nullptr) 75 , m_endBlock(nullptr) 76 , m_typingStyle(nullptr) 77 , m_deleteIntoBlockquoteStyle(nullptr) 78 { 79 } 80 81 DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool expandForSpecialElements, bool sanitizeMarkup) 82 : CompositeEditCommand(*selection.start().document()) 83 , m_hasSelectionToDelete(true) 84 , m_smartDelete(smartDelete) 85 , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete) 86 , m_needPlaceholder(false) 87 , m_expandForSpecialElements(expandForSpecialElements) 88 , m_pruneStartBlockIfNecessary(false) 89 , m_startsAtEmptyLine(false) 90 , m_sanitizeMarkup(sanitizeMarkup) 91 , m_selectionToDelete(selection) 92 , m_startBlock(nullptr) 93 , m_endBlock(nullptr) 94 , m_typingStyle(nullptr) 95 , m_deleteIntoBlockquoteStyle(nullptr) 96 { 97 } 98 99 void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) 100 { 101 Node* startSpecialContainer = 0; 102 Node* endSpecialContainer = 0; 103 104 start = m_selectionToDelete.start(); 105 end = m_selectionToDelete.end(); 106 107 // For HRs, we'll get a position at (HR,1) when hitting delete from the beginning of the previous line, or (HR,0) when forward deleting, 108 // but in these cases, we want to delete it, so manually expand the selection 109 if (isHTMLHRElement(*start.deprecatedNode())) 110 start = positionBeforeNode(start.deprecatedNode()); 111 else if (isHTMLHRElement(*end.deprecatedNode())) 112 end = positionAfterNode(end.deprecatedNode()); 113 114 // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expansion. 115 if (!m_expandForSpecialElements) 116 return; 117 118 while (1) { 119 startSpecialContainer = 0; 120 endSpecialContainer = 0; 121 122 Position s = positionBeforeContainingSpecialElement(start, &startSpecialContainer); 123 Position e = positionAfterContainingSpecialElement(end, &endSpecialContainer); 124 125 if (!startSpecialContainer && !endSpecialContainer) 126 break; 127 128 if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || VisiblePosition(end) != m_selectionToDelete.visibleEnd()) 129 break; 130 131 // If we're going to expand to include the startSpecialContainer, it must be fully selected. 132 if (startSpecialContainer && !endSpecialContainer && comparePositions(positionInParentAfterNode(*startSpecialContainer), end) > -1) 133 break; 134 135 // If we're going to expand to include the endSpecialContainer, it must be fully selected. 136 if (endSpecialContainer && !startSpecialContainer && comparePositions(start, positionInParentBeforeNode(*endSpecialContainer)) > -1) 137 break; 138 139 if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer)) 140 // Don't adjust the end yet, it is the end of a special element that contains the start 141 // special element (which may or may not be fully selected). 142 start = s; 143 else if (endSpecialContainer && endSpecialContainer->isDescendantOf(startSpecialContainer)) 144 // Don't adjust the start yet, it is the start of a special element that contains the end 145 // special element (which may or may not be fully selected). 146 end = e; 147 else { 148 start = s; 149 end = e; 150 } 151 } 152 } 153 154 void DeleteSelectionCommand::setStartingSelectionOnSmartDelete(const Position& start, const Position& end) 155 { 156 bool isBaseFirst = startingSelection().isBaseFirst(); 157 VisiblePosition newBase(isBaseFirst ? start : end); 158 VisiblePosition newExtent(isBaseFirst ? end : start); 159 setStartingSelection(VisibleSelection(newBase, newExtent, startingSelection().isDirectional())); 160 } 161 162 void DeleteSelectionCommand::initializePositionData() 163 { 164 Position start, end; 165 initializeStartEnd(start, end); 166 167 ASSERT(isEditablePosition(start, ContentIsEditable, DoNotUpdateStyle)); 168 if (!isEditablePosition(end, ContentIsEditable, DoNotUpdateStyle)) 169 end = lastEditablePositionBeforePositionInRoot(end, highestEditableRoot(start)); 170 171 m_upstreamStart = start.upstream(); 172 m_downstreamStart = start.downstream(); 173 m_upstreamEnd = end.upstream(); 174 m_downstreamEnd = end.downstream(); 175 176 m_startRoot = editableRootForPosition(start); 177 m_endRoot = editableRootForPosition(end); 178 179 m_startTableRow = enclosingNodeOfType(start, &isHTMLTableRowElement); 180 m_endTableRow = enclosingNodeOfType(end, &isHTMLTableRowElement); 181 182 // Don't move content out of a table cell. 183 // If the cell is non-editable, enclosingNodeOfType won't return it by default, so 184 // tell that function that we don't care if it returns non-editable nodes. 185 Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary); 186 Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary); 187 // FIXME: This isn't right. A borderless table with two rows and a single column would appear as two paragraphs. 188 if (endCell && endCell != startCell) 189 m_mergeBlocksAfterDelete = false; 190 191 // Usually the start and the end of the selection to delete are pulled together as a result of the deletion. 192 // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret 193 // and receive the placeholder after deletion. 194 VisiblePosition visibleEnd(m_downstreamEnd); 195 if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd)) 196 m_endingPosition = m_downstreamEnd; 197 else 198 m_endingPosition = m_downstreamStart; 199 200 // We don't want to merge into a block if it will mean changing the quote level of content after deleting 201 // selections that contain a whole number paragraphs plus a line break, since it is unclear to most users 202 // that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior 203 // for indented paragraphs. 204 // Only apply this rule if the endingSelection is a range selection. If it is a caret, then other operations have created 205 // the selection we're deleting (like the process of creating a selection to delete during a backspace), and the user isn't in the situation described above. 206 if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) 207 && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start)) 208 && endingSelection().isRange()) { 209 m_mergeBlocksAfterDelete = false; 210 m_pruneStartBlockIfNecessary = true; 211 } 212 213 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection 214 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity()); 215 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY); 216 217 if (m_smartDelete) { 218 219 // skip smart delete if the selection to delete already starts or ends with whitespace 220 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent(); 221 bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull(); 222 if (!skipSmartDelete) 223 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull(); 224 225 // extend selection upstream if there is whitespace there 226 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull(); 227 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) { 228 VisiblePosition visiblePos = VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY).previous(); 229 pos = visiblePos.deepEquivalent(); 230 // Expand out one character upstream for smart delete and recalculate 231 // positions based on this change. 232 m_upstreamStart = pos.upstream(); 233 m_downstreamStart = pos.downstream(); 234 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity()); 235 236 setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd); 237 } 238 239 // trailing whitespace is only considered for smart delete if there is no leading 240 // whitespace, as in the case where you double-click the first word of a paragraph. 241 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) { 242 // Expand out one character downstream for smart delete and recalculate 243 // positions based on this change. 244 pos = VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY).next().deepEquivalent(); 245 m_upstreamEnd = pos.upstream(); 246 m_downstreamEnd = pos.downstream(); 247 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY); 248 249 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd); 250 } 251 } 252 253 // We must pass call parentAnchoredEquivalent on the positions since some editing positions 254 // that appear inside their nodes aren't really inside them. [hr, 0] is one example. 255 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing element getters 256 // like the one below, since editing functions should obviously accept editing positions. 257 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable 258 // node. This was done to match existing behavior, but it seems wrong. 259 m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary); 260 m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary); 261 } 262 263 // We don't want to inherit style from an element which can't have contents. 264 static bool shouldNotInheritStyleFrom(const Node& node) 265 { 266 return !node.canContainRangeEndPoint(); 267 } 268 269 void DeleteSelectionCommand::saveTypingStyleState() 270 { 271 // A common case is deleting characters that are all from the same text node. In 272 // that case, the style at the start of the selection before deletion will be the 273 // same as the style at the start of the selection after deletion (since those 274 // two positions will be identical). Therefore there is no need to save the 275 // typing style at the start of the selection, nor is there a reason to 276 // compute the style at the start of the selection after deletion (see the 277 // early return in calculateTypingStyleAfterDelete). 278 if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() && m_upstreamStart.deprecatedNode()->isTextNode()) 279 return; 280 281 if (shouldNotInheritStyleFrom(*m_selectionToDelete.start().anchorNode())) 282 return; 283 284 // Figure out the typing style in effect before the delete is done. 285 m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), EditingStyle::EditingPropertiesInEffect); 286 m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDelete.start())); 287 288 // If we're deleting into a Mail blockquote, save the style at end() instead of start() 289 // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote 290 if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote)) 291 m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end()); 292 else 293 m_deleteIntoBlockquoteStyle = nullptr; 294 } 295 296 bool DeleteSelectionCommand::handleSpecialCaseBRDelete() 297 { 298 Node* nodeAfterUpstreamStart = m_upstreamStart.computeNodeAfterPosition(); 299 Node* nodeAfterDownstreamStart = m_downstreamStart.computeNodeAfterPosition(); 300 // Upstream end will appear before BR due to canonicalization 301 Node* nodeAfterUpstreamEnd = m_upstreamEnd.computeNodeAfterPosition(); 302 303 if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart) 304 return false; 305 306 // Check for special-case where the selection contains only a BR on a line by itself after another BR. 307 bool upstreamStartIsBR = isHTMLBRElement(*nodeAfterUpstreamStart); 308 bool downstreamStartIsBR = isHTMLBRElement(*nodeAfterDownstreamStart); 309 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && nodeAfterDownstreamStart == nodeAfterUpstreamEnd; 310 if (isBROnLineByItself) { 311 removeNode(nodeAfterDownstreamStart); 312 return true; 313 } 314 315 // FIXME: This code doesn't belong in here. 316 // We detect the case where the start is an empty line consisting of BR not wrapped in a block element. 317 if (upstreamStartIsBR && downstreamStartIsBR && !(isStartOfBlock(VisiblePosition(positionBeforeNode(nodeAfterUpstreamStart))) && isEndOfBlock(VisiblePosition(positionAfterNode(nodeAfterUpstreamStart))))) { 318 m_startsAtEmptyLine = true; 319 m_endingPosition = m_downstreamEnd; 320 } 321 322 return false; 323 } 324 325 static Position firstEditablePositionInNode(Node* node) 326 { 327 ASSERT(node); 328 Node* next = node; 329 while (next && !next->rendererIsEditable()) 330 next = NodeTraversal::next(*next, node); 331 return next ? firstPositionInOrBeforeNode(next) : Position(); 332 } 333 334 void DeleteSelectionCommand::removeNode(PassRefPtrWillBeRawPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) 335 { 336 if (!node) 337 return; 338 339 if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.get()) && node->isDescendantOf(m_endRoot.get()))) { 340 // If a node is not in both the start and end editable roots, remove it only if its inside an editable region. 341 if (!node->parentNode()->rendererIsEditable()) { 342 // Don't remove non-editable atomic nodes. 343 if (!node->firstChild()) 344 return; 345 // Search this non-editable region for editable regions to empty. 346 RefPtrWillBeRawPtr<Node> child = node->firstChild(); 347 while (child) { 348 RefPtrWillBeRawPtr<Node> nextChild = child->nextSibling(); 349 removeNode(child.get(), shouldAssumeContentIsAlwaysEditable); 350 // Bail if nextChild is no longer node's child. 351 if (nextChild && nextChild->parentNode() != node) 352 return; 353 child = nextChild; 354 } 355 356 // Don't remove editable regions that are inside non-editable ones, just clear them. 357 return; 358 } 359 } 360 361 if (isTableStructureNode(node.get()) || node->isRootEditableElement()) { 362 // Do not remove an element of table structure; remove its contents. 363 // Likewise for the root editable element. 364 Node* child = node->firstChild(); 365 while (child) { 366 Node* remove = child; 367 child = child->nextSibling(); 368 removeNode(remove, shouldAssumeContentIsAlwaysEditable); 369 } 370 371 // Make sure empty cell has some height, if a placeholder can be inserted. 372 document().updateLayoutIgnorePendingStylesheets(); 373 RenderObject *r = node->renderer(); 374 if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0) { 375 Position firstEditablePosition = firstEditablePositionInNode(node.get()); 376 if (firstEditablePosition.isNotNull()) 377 insertBlockPlaceholder(firstEditablePosition); 378 } 379 return; 380 } 381 382 if (node == m_startBlock) { 383 VisiblePosition previous = VisiblePosition(firstPositionInNode(m_startBlock.get())).previous(); 384 if (previous.isNotNull() && !isEndOfBlock(previous)) 385 m_needPlaceholder = true; 386 } 387 if (node == m_endBlock) { 388 VisiblePosition next = VisiblePosition(lastPositionInNode(m_endBlock.get())).next(); 389 if (next.isNotNull() && !isStartOfBlock(next)) 390 m_needPlaceholder = true; 391 } 392 393 // FIXME: Update the endpoints of the range being deleted. 394 updatePositionForNodeRemoval(m_endingPosition, *node); 395 updatePositionForNodeRemoval(m_leadingWhitespace, *node); 396 updatePositionForNodeRemoval(m_trailingWhitespace, *node); 397 398 CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable); 399 } 400 401 static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position) 402 { 403 if (position.anchorType() != Position::PositionIsOffsetInAnchor || position.containerNode() != node) 404 return; 405 406 if (position.offsetInContainerNode() > offset + count) 407 position.moveToOffset(position.offsetInContainerNode() - count); 408 else if (position.offsetInContainerNode() > offset) 409 position.moveToOffset(offset); 410 } 411 412 void DeleteSelectionCommand::deleteTextFromNode(PassRefPtrWillBeRawPtr<Text> node, unsigned offset, unsigned count) 413 { 414 // FIXME: Update the endpoints of the range being deleted. 415 updatePositionForTextRemoval(node.get(), offset, count, m_endingPosition); 416 updatePositionForTextRemoval(node.get(), offset, count, m_leadingWhitespace); 417 updatePositionForTextRemoval(node.get(), offset, count, m_trailingWhitespace); 418 updatePositionForTextRemoval(node.get(), offset, count, m_downstreamEnd); 419 420 CompositeEditCommand::deleteTextFromNode(node, offset, count); 421 } 422 423 void DeleteSelectionCommand::makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss() 424 { 425 RefPtrWillBeRawPtr<Range> range = m_selectionToDelete.toNormalizedRange(); 426 RefPtrWillBeRawPtr<Node> node = range->firstNode(); 427 while (node && node != range->pastLastNode()) { 428 RefPtrWillBeRawPtr<Node> nextNode = NodeTraversal::next(*node); 429 if ((isHTMLStyleElement(*node) && !(toElement(node)->hasAttribute(scopedAttr))) || isHTMLLinkElement(*node)) { 430 nextNode = NodeTraversal::nextSkippingChildren(*node); 431 RefPtrWillBeRawPtr<ContainerNode> rootEditableElement = node->rootEditableElement(); 432 if (rootEditableElement.get()) { 433 removeNode(node); 434 appendNode(node, rootEditableElement); 435 } 436 } 437 node = nextNode; 438 } 439 } 440 441 void DeleteSelectionCommand::handleGeneralDelete() 442 { 443 if (m_upstreamStart.isNull()) 444 return; 445 446 int startOffset = m_upstreamStart.deprecatedEditingOffset(); 447 Node* startNode = m_upstreamStart.deprecatedNode(); 448 ASSERT(startNode); 449 450 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss(); 451 452 // Never remove the start block unless it's a table, in which case we won't merge content in. 453 if (startNode->isSameNode(m_startBlock.get()) && !startOffset && canHaveChildrenForEditing(startNode) && !isHTMLTableElement(*startNode)) { 454 startOffset = 0; 455 startNode = NodeTraversal::next(*startNode); 456 if (!startNode) 457 return; 458 } 459 460 if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) { 461 Text* text = toText(startNode); 462 if (text->length() > (unsigned)caretMaxOffset(startNode)) 463 deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode)); 464 } 465 466 if (startOffset >= lastOffsetForEditing(startNode)) { 467 startNode = NodeTraversal::nextSkippingChildren(*startNode); 468 startOffset = 0; 469 } 470 471 // Done adjusting the start. See if we're all done. 472 if (!startNode) 473 return; 474 475 if (startNode == m_downstreamEnd.deprecatedNode()) { 476 if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) { 477 if (startNode->isTextNode()) { 478 // in a text node that needs to be trimmed 479 Text* text = toText(startNode); 480 deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset); 481 } else { 482 removeChildrenInRange(startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset()); 483 m_endingPosition = m_upstreamStart; 484 } 485 } 486 487 // The selection to delete is all in one node. 488 if (!startNode->renderer() || (!startOffset && m_downstreamEnd.atLastEditingPositionForNode())) 489 removeNode(startNode); 490 } 491 else { 492 bool startNodeWasDescendantOfEndNode = m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()); 493 // The selection to delete spans more than one node. 494 RefPtrWillBeRawPtr<Node> node(startNode); 495 496 if (startOffset > 0) { 497 if (startNode->isTextNode()) { 498 // in a text node that needs to be trimmed 499 Text* text = toText(node); 500 deleteTextFromNode(text, startOffset, text->length() - startOffset); 501 node = NodeTraversal::next(*node); 502 } else { 503 node = startNode->traverseToChildAt(startOffset); 504 } 505 } else if (startNode == m_upstreamEnd.deprecatedNode() && startNode->isTextNode()) { 506 Text* text = toText(m_upstreamEnd.deprecatedNode()); 507 deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset()); 508 } 509 510 // handle deleting all nodes that are completely selected 511 while (node && node != m_downstreamEnd.deprecatedNode()) { 512 if (comparePositions(firstPositionInOrBeforeNode(node.get()), m_downstreamEnd) >= 0) { 513 // NodeTraversal::nextSkippingChildren just blew past the end position, so stop deleting 514 node = nullptr; 515 } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.get())) { 516 RefPtrWillBeRawPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(*node); 517 // if we just removed a node from the end container, update end position so the 518 // check above will work 519 updatePositionForNodeRemoval(m_downstreamEnd, *node); 520 removeNode(node.get()); 521 node = nextNode.get(); 522 } else { 523 Node& n = node->lastDescendantOrSelf(); 524 if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(&n)) { 525 removeNode(node.get()); 526 node = nullptr; 527 } else { 528 node = NodeTraversal::next(*node); 529 } 530 } 531 } 532 533 if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode()) && m_downstreamEnd.inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode())) { 534 if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.deprecatedNode())) { 535 // The node itself is fully selected, not just its contents. Delete it. 536 removeNode(m_downstreamEnd.deprecatedNode()); 537 } else { 538 if (m_downstreamEnd.deprecatedNode()->isTextNode()) { 539 // in a text node that needs to be trimmed 540 Text* text = toText(m_downstreamEnd.deprecatedNode()); 541 if (m_downstreamEnd.deprecatedEditingOffset() > 0) { 542 deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset()); 543 } 544 // Remove children of m_downstreamEnd.deprecatedNode() that come after m_upstreamStart. 545 // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.deprecatedNode() 546 // and m_upstreamStart has been removed from the document, because then we don't 547 // know how many children to remove. 548 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can 549 // always know which children to remove. 550 } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.inDocument())) { 551 int offset = 0; 552 if (m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())) { 553 Node* n = m_upstreamStart.deprecatedNode(); 554 while (n && n->parentNode() != m_downstreamEnd.deprecatedNode()) 555 n = n->parentNode(); 556 if (n) 557 offset = n->nodeIndex() + 1; 558 } 559 removeChildrenInRange(m_downstreamEnd.deprecatedNode(), offset, m_downstreamEnd.deprecatedEditingOffset()); 560 m_downstreamEnd = createLegacyEditingPosition(m_downstreamEnd.deprecatedNode(), offset); 561 } 562 } 563 } 564 } 565 } 566 567 void DeleteSelectionCommand::fixupWhitespace() 568 { 569 document().updateLayoutIgnorePendingStylesheets(); 570 // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore 571 if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && m_leadingWhitespace.deprecatedNode()->isTextNode()) { 572 Text* textNode = toText(m_leadingWhitespace.deprecatedNode()); 573 ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); 574 replaceTextInNodePreservingMarkers(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 575 } 576 if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && m_trailingWhitespace.deprecatedNode()->isTextNode()) { 577 Text* textNode = toText(m_trailingWhitespace.deprecatedNode()); 578 ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace()); 579 replaceTextInNodePreservingMarkers(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); 580 } 581 } 582 583 // If a selection starts in one block and ends in another, we have to merge to bring content before the 584 // start together with content after the end. 585 void DeleteSelectionCommand::mergeParagraphs() 586 { 587 if (!m_mergeBlocksAfterDelete) { 588 if (m_pruneStartBlockIfNecessary) { 589 // We aren't going to merge into the start block, so remove it if it's empty. 590 prune(m_startBlock); 591 // Removing the start block during a deletion is usually an indication that we need 592 // a placeholder, but not in this case. 593 m_needPlaceholder = false; 594 } 595 return; 596 } 597 598 // It shouldn't have been asked to both try and merge content into the start block and prune it. 599 ASSERT(!m_pruneStartBlockIfNecessary); 600 601 // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839). 602 if (!m_downstreamEnd.inDocument() || !m_upstreamStart.inDocument()) 603 return; 604 605 // FIXME: The deletion algorithm shouldn't let this happen. 606 if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0) 607 return; 608 609 // There's nothing to merge. 610 if (m_upstreamStart == m_downstreamEnd) 611 return; 612 613 VisiblePosition startOfParagraphToMove(m_downstreamEnd); 614 VisiblePosition mergeDestination(m_upstreamStart); 615 616 // m_downstreamEnd's block has been emptied out by deletion. There is no content inside of it to 617 // move, so just remove it. 618 Element* endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode()); 619 if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) { 620 removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode())); 621 return; 622 } 623 624 // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion. 625 if (!mergeDestination.deepEquivalent().deprecatedNode() || (!mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) && (!mergeDestination.deepEquivalent().anchorNode()->firstChild() || !m_upstreamStart.containerNode()->firstChild())) || (m_startsAtEmptyLine && mergeDestination != startOfParagraphToMove)) { 626 insertNodeAt(createBreakElement(document()).get(), m_upstreamStart); 627 mergeDestination = VisiblePosition(m_upstreamStart); 628 } 629 630 if (mergeDestination == startOfParagraphToMove) 631 return; 632 633 VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove, CanSkipOverEditingBoundary); 634 635 if (mergeDestination == endOfParagraphToMove) 636 return; 637 638 // If the merge destination and source to be moved are both list items of different lists, merge them into single list. 639 Node* listItemInFirstParagraph = enclosingNodeOfType(m_upstreamStart, isListItem); 640 Node* listItemInSecondParagraph = enclosingNodeOfType(m_downstreamEnd, isListItem); 641 if (listItemInFirstParagraph && listItemInSecondParagraph 642 && listItemInFirstParagraph->parentElement() != listItemInSecondParagraph->parentElement() 643 && canMergeLists(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement())) { 644 mergeIdenticalElements(listItemInFirstParagraph->parentElement(), listItemInSecondParagraph->parentElement()); 645 m_endingPosition = mergeDestination.deepEquivalent(); 646 return; 647 } 648 649 // The rule for merging into an empty block is: only do so if its farther to the right. 650 // FIXME: Consider RTL. 651 if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) { 652 if (isHTMLBRElement(*mergeDestination.deepEquivalent().downstream().deprecatedNode())) { 653 removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().deprecatedNode()); 654 m_endingPosition = startOfParagraphToMove.deepEquivalent(); 655 return; 656 } 657 } 658 659 // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination. If there is 660 // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the caret to just before the selection we deleted. 661 // See https://bugs.webkit.org/show_bug.cgi?id=25439 662 if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) { 663 m_endingPosition = m_upstreamStart; 664 return; 665 } 666 667 // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block 668 // removals that it does cause the insertion of *another* placeholder. 669 bool needPlaceholder = m_needPlaceholder; 670 bool paragraphToMergeIsEmpty = (startOfParagraphToMove == endOfParagraphToMove); 671 moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, false, !paragraphToMergeIsEmpty); 672 m_needPlaceholder = needPlaceholder; 673 // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph). 674 m_endingPosition = endingSelection().start(); 675 } 676 677 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows() 678 { 679 if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow) { 680 Node* row = m_endTableRow->previousSibling(); 681 while (row && row != m_startTableRow) { 682 RefPtrWillBeRawPtr<Node> previousRow = row->previousSibling(); 683 if (isTableRowEmpty(row)) 684 // Use a raw removeNode, instead of DeleteSelectionCommand's, because 685 // that won't remove rows, it only empties them in preparation for this function. 686 CompositeEditCommand::removeNode(row); 687 row = previousRow.get(); 688 } 689 } 690 691 // Remove empty rows after the start row. 692 if (m_startTableRow && m_startTableRow->inDocument() && m_startTableRow != m_endTableRow) { 693 Node* row = m_startTableRow->nextSibling(); 694 while (row && row != m_endTableRow) { 695 RefPtrWillBeRawPtr<Node> nextRow = row->nextSibling(); 696 if (isTableRowEmpty(row)) 697 CompositeEditCommand::removeNode(row); 698 row = nextRow.get(); 699 } 700 } 701 702 if (m_endTableRow && m_endTableRow->inDocument() && m_endTableRow != m_startTableRow) 703 if (isTableRowEmpty(m_endTableRow.get())) { 704 // Don't remove m_endTableRow if it's where we're putting the ending selection. 705 if (!m_endingPosition.deprecatedNode()->isDescendantOf(m_endTableRow.get())) { 706 // FIXME: We probably shouldn't remove m_endTableRow unless it's fully selected, even if it is empty. 707 // We'll need to start adjusting the selection endpoints during deletion to know whether or not m_endTableRow 708 // was fully selected here. 709 CompositeEditCommand::removeNode(m_endTableRow.get()); 710 } 711 } 712 } 713 714 void DeleteSelectionCommand::calculateTypingStyleAfterDelete() 715 { 716 // Clearing any previously set typing style and doing an early return. 717 if (!m_typingStyle) { 718 document().frame()->selection().clearTypingStyle(); 719 return; 720 } 721 722 // Compute the difference between the style before the delete and the style now 723 // after the delete has been done. Set this style on the frame, so other editing 724 // commands being composed with this one will work, and also cache it on the command, 725 // so the LocalFrame::appliedEditing can set it after the whole composite command 726 // has completed. 727 728 // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style 729 if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary)) 730 m_typingStyle = m_deleteIntoBlockquoteStyle; 731 m_deleteIntoBlockquoteStyle = nullptr; 732 733 m_typingStyle->prepareToApplyAt(m_endingPosition); 734 if (m_typingStyle->isEmpty()) 735 m_typingStyle = nullptr; 736 // This is where we've deleted all traces of a style but not a whole paragraph (that's handled above). 737 // In this case if we start typing, the new characters should have the same style as the just deleted ones, 738 // but, if we change the selection, come back and start typing that style should be lost. Also see 739 // preserveTypingStyle() below. 740 document().frame()->selection().setTypingStyle(m_typingStyle); 741 } 742 743 void DeleteSelectionCommand::clearTransientState() 744 { 745 m_selectionToDelete = VisibleSelection(); 746 m_upstreamStart.clear(); 747 m_downstreamStart.clear(); 748 m_upstreamEnd.clear(); 749 m_downstreamEnd.clear(); 750 m_endingPosition.clear(); 751 m_leadingWhitespace.clear(); 752 m_trailingWhitespace.clear(); 753 } 754 755 // This method removes div elements with no attributes that have only one child or no children at all. 756 void DeleteSelectionCommand::removeRedundantBlocks() 757 { 758 Node* node = m_endingPosition.containerNode(); 759 Node* rootNode = node->rootEditableElement(); 760 761 while (node != rootNode) { 762 if (isRemovableBlock(node)) { 763 if (node == m_endingPosition.anchorNode()) 764 updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node); 765 766 CompositeEditCommand::removeNodePreservingChildren(node); 767 node = m_endingPosition.anchorNode(); 768 } else 769 node = node->parentNode(); 770 } 771 } 772 773 void DeleteSelectionCommand::doApply() 774 { 775 // If selection has not been set to a custom selection when the command was created, 776 // use the current ending selection. 777 if (!m_hasSelectionToDelete) 778 m_selectionToDelete = endingSelection(); 779 780 if (!m_selectionToDelete.isNonOrphanedRange()) 781 return; 782 783 // save this to later make the selection with 784 EAffinity affinity = m_selectionToDelete.affinity(); 785 786 Position downstreamEnd = m_selectionToDelete.end().downstream(); 787 bool rootWillStayOpenWithoutPlaceholder = downstreamEnd.containerNode() == downstreamEnd.containerNode()->rootEditableElement() 788 || (downstreamEnd.containerNode()->isTextNode() && downstreamEnd.containerNode()->parentNode() == downstreamEnd.containerNode()->rootEditableElement()); 789 bool lineBreakAtEndOfSelectionToDelete = lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd()); 790 m_needPlaceholder = !rootWillStayOpenWithoutPlaceholder 791 && isStartOfParagraph(m_selectionToDelete.visibleStart(), CanCrossEditingBoundary) 792 && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditingBoundary) 793 && !lineBreakAtEndOfSelectionToDelete; 794 if (m_needPlaceholder) { 795 // Don't need a placeholder when deleting a selection that starts just before a table 796 // and ends inside it (we do need placeholders to hold open empty cells, but that's 797 // handled elsewhere). 798 if (Node* table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart())) 799 if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table)) 800 m_needPlaceholder = false; 801 } 802 803 804 // set up our state 805 initializePositionData(); 806 807 bool lineBreakBeforeStart = lineBreakExistsAtVisiblePosition(VisiblePosition(m_upstreamStart).previous()); 808 809 // Delete any text that may hinder our ability to fixup whitespace after the delete 810 deleteInsignificantTextDownstream(m_trailingWhitespace); 811 812 saveTypingStyleState(); 813 814 // deleting just a BR is handled specially, at least because we do not 815 // want to replace it with a placeholder BR! 816 if (handleSpecialCaseBRDelete()) { 817 calculateTypingStyleAfterDelete(); 818 setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional())); 819 clearTransientState(); 820 rebalanceWhitespace(); 821 return; 822 } 823 824 handleGeneralDelete(); 825 826 fixupWhitespace(); 827 828 mergeParagraphs(); 829 830 removePreviouslySelectedEmptyTableRows(); 831 832 if (!m_needPlaceholder && rootWillStayOpenWithoutPlaceholder) { 833 VisiblePosition visualEnding(m_endingPosition); 834 bool hasPlaceholder = lineBreakExistsAtVisiblePosition(visualEnding) 835 && visualEnding.next(CannotCrossEditingBoundary).isNull(); 836 m_needPlaceholder = hasPlaceholder && lineBreakBeforeStart && !lineBreakAtEndOfSelectionToDelete; 837 } 838 839 RefPtrWillBeRawPtr<Node> placeholder = m_needPlaceholder ? createBreakElement(document()) : nullptr; 840 841 if (placeholder) { 842 if (m_sanitizeMarkup) 843 removeRedundantBlocks(); 844 insertNodeAt(placeholder.get(), m_endingPosition); 845 } 846 847 rebalanceWhitespaceAt(m_endingPosition); 848 849 calculateTypingStyleAfterDelete(); 850 851 setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional())); 852 clearTransientState(); 853 } 854 855 EditAction DeleteSelectionCommand::editingAction() const 856 { 857 // Note that DeleteSelectionCommand is also used when the user presses the Delete key, 858 // but in that case there's a TypingCommand that supplies the editingAction(), so 859 // the Undo menu correctly shows "Undo Typing" 860 return EditActionCut; 861 } 862 863 // Normally deletion doesn't preserve the typing style that was present before it. For example, 864 // type a character, Bold, then delete the character and start typing. The Bold typing style shouldn't 865 // stick around. Deletion should preserve a typing style that *it* sets, however. 866 bool DeleteSelectionCommand::preservesTypingStyle() const 867 { 868 return m_typingStyle; 869 } 870 871 void DeleteSelectionCommand::trace(Visitor* visitor) 872 { 873 visitor->trace(m_selectionToDelete); 874 visitor->trace(m_upstreamStart); 875 visitor->trace(m_downstreamStart); 876 visitor->trace(m_upstreamEnd); 877 visitor->trace(m_downstreamEnd); 878 visitor->trace(m_endingPosition); 879 visitor->trace(m_leadingWhitespace); 880 visitor->trace(m_trailingWhitespace); 881 visitor->trace(m_startBlock); 882 visitor->trace(m_endBlock); 883 visitor->trace(m_typingStyle); 884 visitor->trace(m_deleteIntoBlockquoteStyle); 885 visitor->trace(m_startRoot); 886 visitor->trace(m_endRoot); 887 visitor->trace(m_startTableRow); 888 visitor->trace(m_endTableRow); 889 visitor->trace(m_temporaryPlaceholder); 890 CompositeEditCommand::trace(visitor); 891 } 892 893 } // namespace WebCore 894