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