blob: afa23a8432079c9af827213aef1613644de02d12 [file] [log] [blame]
mjsa0fe4042005-05-13 08:37:15 +00001/*
mjs@apple.com92047332014-03-15 04:08:27 +00002 * Copyright (C) 2005 Apple Inc. All rights reserved.
mjsa0fe4042005-05-13 08:37:15 +00003 *
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 *
mjs@apple.com92047332014-03-15 04:08:27 +000013 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
mjsa0fe4042005-05-13 08:37:15 +000014 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
mjs@apple.com92047332014-03-15 04:08:27 +000016 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
mjsa0fe4042005-05-13 08:37:15 +000017 * 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
mjsb64c50a2005-10-03 21:13:12 +000026#include "config.h"
darina68e0432006-02-14 21:40:54 +000027#include "DeleteSelectionCommand.h"
mjsa0fe4042005-05-13 08:37:15 +000028
darinb9481ed2006-03-20 02:57:59 +000029#include "Document.h"
jpu@apple.comfd2014c2011-05-12 00:54:08 +000030#include "DocumentMarkerController.h"
commit-queue@webkit.org02ea7a22017-03-03 06:35:25 +000031#include "Editing.h"
justing5745f952006-11-11 01:56:24 +000032#include "Editor.h"
justingf90e28d2007-07-30 18:25:19 +000033#include "EditorClient.h"
zalan@apple.com53862672017-02-10 22:36:12 +000034#include "ElementIterator.h"
mjs810e9762006-01-09 21:39:14 +000035#include "Frame.h"
darin@apple.comd385be42016-05-14 20:09:50 +000036#include "HTMLBRElement.h"
cdumez@apple.coma3bc43f2014-09-30 19:59:49 +000037#include "HTMLLinkElement.h"
darin98fa8b82006-03-20 08:03:57 +000038#include "HTMLNames.h"
cdumez@apple.coma3bc43f2014-09-30 19:59:49 +000039#include "HTMLStyleElement.h"
kangil.han@samsung.comb44a6812013-07-08 06:52:41 +000040#include "HTMLTableElement.h"
antti@apple.com5d47b582012-12-11 00:13:29 +000041#include "NodeTraversal.h"
darin@apple.com2e2bfd72020-07-29 16:36:40 +000042#include "Range.h"
hyatt@apple.comd885df72009-01-22 02:31:52 +000043#include "RenderTableCell.h"
antti@apple.come9c90162013-10-13 16:00:10 +000044#include "RenderText.h"
cdumez@apple.com426611c2014-10-20 05:17:06 +000045#include "RenderedDocumentMarker.h"
rniwa@webkit.org098af8c2021-04-25 05:59:59 +000046#include "ScriptDisallowedScope.h"
darin42563ac52007-01-22 17:28:57 +000047#include "Text.h"
tkent@chromium.org8c35c122013-03-06 13:00:14 +000048#include "VisibleUnits.h"
mjsa0fe4042005-05-13 08:37:15 +000049
darinbbe64662006-01-16 17:52:23 +000050namespace WebCore {
darinedbc5e42005-08-25 23:13:58 +000051
darinbbe64662006-01-16 17:52:23 +000052using namespace HTMLNames;
mjsa0fe4042005-05-13 08:37:15 +000053
justing94b6f9f2007-10-31 01:27:54 +000054static bool isTableRow(const Node* node)
justing187d2922007-07-17 21:05:21 +000055{
56 return node && node->hasTagName(trTag);
57}
58
justing13e16aa2007-07-17 21:55:05 +000059static bool isTableCellEmpty(Node* cell)
justing187d2922007-07-17 21:05:21 +000060{
61 ASSERT(isTableCell(cell));
leviw@chromium.org1f953622011-03-15 19:36:51 +000062 return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPositionInNode(cell));
justing187d2922007-07-17 21:05:21 +000063}
64
justing13e16aa2007-07-17 21:55:05 +000065static bool isTableRowEmpty(Node* row)
justing187d2922007-07-17 21:05:21 +000066{
67 if (!isTableRow(row))
68 return false;
rniwa@webkit.org098af8c2021-04-25 05:59:59 +000069
cdumez@apple.come1840be2021-09-20 21:45:40 +000070 for (RefPtr child = row->firstChild(); child; child = child->nextSibling()) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +000071 if (isTableCell(child.get()) && !isTableCellEmpty(child.get()))
justing187d2922007-07-17 21:05:21 +000072 return false;
rniwa@webkit.org098af8c2021-04-25 05:59:59 +000073 }
74
justing187d2922007-07-17 21:05:21 +000075 return true;
76}
77
rniwa@webkit.org098af8c2021-04-25 05:59:59 +000078static bool isSpecialHTMLElement(const Node& node)
79{
80 ScriptDisallowedScope scriptDisallowedScope;
81
82 if (!is<HTMLElement>(node))
83 return false;
84
85 if (downcast<HTMLElement>(node).isLink())
86 return true;
87
88 auto* renderer = downcast<HTMLElement>(node).renderer();
89 if (!renderer)
90 return false;
91
92 if (renderer->style().display() == DisplayType::Table || renderer->style().display() == DisplayType::InlineTable)
93 return true;
94
95 if (renderer->style().isFloating())
96 return true;
97
98 if (renderer->style().position() != PositionType::Static)
99 return true;
100
101 return false;
102}
103
104static RefPtr<HTMLElement> firstInSpecialElement(const Position& position)
105{
cdumez@apple.come1840be2021-09-20 21:45:40 +0000106 RefPtr rootEditableElement { position.rootEditableElement() };
107 for (RefPtr node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000108 if (!isSpecialHTMLElement(*node))
109 continue;
110 VisiblePosition visiblePosition = position;
111 VisiblePosition firstInElement = firstPositionInOrBeforeNode(node.get());
112 if ((isRenderedTable(node.get()) && visiblePosition == firstInElement.next()) || visiblePosition == firstInElement) {
113 RELEASE_ASSERT(is<HTMLElement>(node));
cdumez@apple.com0db47662022-04-08 18:41:53 +0000114 return static_pointer_cast<HTMLElement>(WTFMove(node));
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000115 }
116 }
117 return nullptr;
118}
119
120static RefPtr<HTMLElement> lastInSpecialElement(const Position& position)
121{
cdumez@apple.come1840be2021-09-20 21:45:40 +0000122 RefPtr rootEditableElement { position.rootEditableElement() };
123 for (RefPtr node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000124 if (!isSpecialHTMLElement(*node))
125 continue;
126 VisiblePosition visiblePosition = position;
127 VisiblePosition lastInElement = lastPositionInOrAfterNode(node.get());
128 if ((isRenderedTable(node.get()) && visiblePosition == lastInElement.previous()) || visiblePosition == lastInElement) {
129 RELEASE_ASSERT(is<HTMLElement>(node));
cdumez@apple.com0db47662022-04-08 18:41:53 +0000130 return static_pointer_cast<HTMLElement>(WTFMove(node));
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000131 }
132 }
133 return nullptr;
134}
135
136static std::pair<Position, RefPtr<HTMLElement>> positionBeforeContainingSpecialElement(const Position& position)
137{
138 auto element = firstInSpecialElement(position);
139 if (!element)
140 return { position, nullptr };
141 auto result = positionInParentBeforeNode(element.get());
142 if (result.isNull() || result.containerNode()->rootEditableElement() != position.containerNode()->rootEditableElement())
143 return { position, nullptr };
144 return { result, WTFMove(element) };
145}
146
147static std::pair<Position, RefPtr<HTMLElement>> positionAfterContainingSpecialElement(const Position& position)
148{
149 auto element = lastInSpecialElement(position);
150 if (!element)
151 return { position, nullptr };
152 auto result = positionInParentAfterNode(element.get());
153 if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.containerNode()->rootEditableElement())
154 return { position, nullptr };
155 return { result, WTFMove(element) };
156}
157
commit-queue@webkit.org2de6e052015-04-26 22:17:11 +0000158DeleteSelectionCommand::DeleteSelectionCommand(Document& document, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup, EditAction editingAction)
159 : CompositeEditCommand(document, editingAction)
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000160 , m_hasSelectionToDelete(false)
161 , m_smartDelete(smartDelete)
162 , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
163 , m_needPlaceholder(false)
164 , m_replace(replace)
165 , m_expandForSpecialElements(expandForSpecialElements)
166 , m_pruneStartBlockIfNecessary(false)
167 , m_startsAtEmptyLine(false)
enrica@apple.comd14c2a82012-05-07 23:30:08 +0000168 , m_sanitizeMarkup(sanitizeMarkup)
mjsa0fe4042005-05-13 08:37:15 +0000169{
170}
171
commit-queue@webkit.org2de6e052015-04-26 22:17:11 +0000172DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup, EditAction editingAction)
173 : CompositeEditCommand(selection.start().anchorNode()->document(), editingAction)
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000174 , m_hasSelectionToDelete(true)
175 , m_smartDelete(smartDelete)
176 , m_mergeBlocksAfterDelete(mergeBlocksAfterDelete)
177 , m_needPlaceholder(false)
178 , m_replace(replace)
179 , m_expandForSpecialElements(expandForSpecialElements)
180 , m_pruneStartBlockIfNecessary(false)
181 , m_startsAtEmptyLine(false)
enrica@apple.comd14c2a82012-05-07 23:30:08 +0000182 , m_sanitizeMarkup(sanitizeMarkup)
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000183 , m_selectionToDelete(selection)
mjsa0fe4042005-05-13 08:37:15 +0000184{
185}
186
justing113aaf32007-01-25 01:00:36 +0000187void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end)
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000188{
justing113aaf32007-01-25 01:00:36 +0000189 start = m_selectionToDelete.start();
190 end = m_selectionToDelete.end();
justinge6f80c12006-06-09 04:57:51 +0000191
justing2112d432006-11-28 21:25:28 +0000192 // 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,
lweintraub635ec2a2006-07-13 18:28:28 +0000193 // but in these cases, we want to delete it, so manually expand the selection
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000194 if (start.deprecatedNode()->hasTagName(hrTag))
195 start = positionBeforeNode(start.deprecatedNode());
196 else if (end.deprecatedNode()->hasTagName(hrTag))
197 end = positionAfterNode(end.deprecatedNode());
lweintraub635ec2a2006-07-13 18:28:28 +0000198
dbates@webkit.orgef42d382010-01-25 19:20:06 +0000199 // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expansion.
justing113aaf32007-01-25 01:00:36 +0000200 if (!m_expandForSpecialElements)
201 return;
202
justin.garcia@apple.com64125512008-02-20 17:48:44 +0000203 while (1) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000204 auto [startBeforeSpecialElement, startSpecialContainer] = positionBeforeContainingSpecialElement(start);
205 auto [endAfterSpecialElement, endSpecialContainer] = positionAfterContainingSpecialElement(end);
206
justing47d836d2007-06-08 00:02:06 +0000207 if (!startSpecialContainer && !endSpecialContainer)
justinge6f80c12006-06-09 04:57:51 +0000208 break;
commit-queue@webkit.orgc8932f82013-10-11 06:18:44 +0000209
210 m_mergeBlocksAfterDelete = false;
211
justin.garcia@apple.com64125512008-02-20 17:48:44 +0000212 if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || VisiblePosition(end) != m_selectionToDelete.visibleEnd())
213 break;
eric@webkit.org05d6e932009-06-02 21:31:08 +0000214
eric@webkit.orgf74ae5c2009-09-09 23:25:47 +0000215 // If we're going to expand to include the startSpecialContainer, it must be fully selected.
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000216 if (startSpecialContainer && !endSpecialContainer && positionInParentAfterNode(startSpecialContainer.get()) >= end)
justing47d836d2007-06-08 00:02:06 +0000217 break;
218
eric@webkit.org05d6e932009-06-02 21:31:08 +0000219 // If we're going to expand to include the endSpecialContainer, it must be fully selected.
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000220 if (endSpecialContainer && !startSpecialContainer && start >= positionInParentBeforeNode(endSpecialContainer.get()))
justing47d836d2007-06-08 00:02:06 +0000221 break;
eric@webkit.orgf74ae5c2009-09-09 23:25:47 +0000222
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000223 if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer.get())) {
justing2bdb03e2006-11-30 02:54:19 +0000224 // Don't adjust the end yet, it is the end of a special element that contains the start
225 // special element (which may or may not be fully selected).
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000226 start = startBeforeSpecialElement;
227 } else if (endSpecialContainer && endSpecialContainer->isDescendantOf(startSpecialContainer.get())) {
justing2bdb03e2006-11-30 02:54:19 +0000228 // Don't adjust the start yet, it is the start of a special element that contains the end
229 // special element (which may or may not be fully selected).
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000230 end = endAfterSpecialElement;
231 } else {
232 start = startBeforeSpecialElement;
233 end = endAfterSpecialElement;
justing2bdb03e2006-11-30 02:54:19 +0000234 }
harrisondec5e092006-01-25 21:05:02 +0000235 }
harrisondec5e092006-01-25 21:05:02 +0000236}
237
ojan@chromium.org078aa802010-03-10 00:17:11 +0000238void DeleteSelectionCommand::setStartingSelectionOnSmartDelete(const Position& start, const Position& end)
ojan@chromium.org77dae9d2010-03-09 23:41:38 +0000239{
240 VisiblePosition newBase;
241 VisiblePosition newExtent;
242 if (startingSelection().isBaseFirst()) {
243 newBase = start;
244 newExtent = end;
245 } else {
246 newBase = end;
247 newExtent = start;
248 }
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +0000249 setStartingSelection(VisibleSelection(newBase, newExtent, startingSelection().isDirectional()));
ojan@chromium.org77dae9d2010-03-09 23:41:38 +0000250}
251
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000252bool DeleteSelectionCommand::shouldSmartDeleteParagraphSpacers()
253{
254 return document().editingBehavior().shouldSmartInsertDeleteParagraphs();
255}
256
257void DeleteSelectionCommand::smartDeleteParagraphSpacers()
258{
259 VisiblePosition visibleStart { m_upstreamStart };
260 VisiblePosition visibleEnd { m_downstreamEnd };
261 bool selectionEndsInParagraphSeperator = isEndOfParagraph(visibleEnd);
262 bool selectionEndIsEndOfContent = endOfEditableContent(visibleEnd) == visibleEnd;
263 bool startAndEndInSameUnsplittableElement = unsplittableElementForPosition(visibleStart.deepEquivalent()) == unsplittableElementForPosition(visibleEnd.deepEquivalent());
264 visibleStart = visibleStart.previous(CannotCrossEditingBoundary);
265 visibleEnd = visibleEnd.next(CannotCrossEditingBoundary);
megan_gardner@apple.comffce9df2020-06-25 20:57:59 +0000266 bool previousPositionIsStartOfContent = startOfEditableContent(visibleStart) == visibleStart;
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000267 bool previousPositionIsBlankParagraph = isBlankParagraph(visibleStart);
megan_gardner@apple.com35d11892020-06-25 22:25:39 +0000268 bool endPositionIsBlankParagraph = isBlankParagraph(visibleEnd);
269 bool hasBlankParagraphAfterEndOrIsEndOfContent = !selectionEndIsEndOfContent && (endPositionIsBlankParagraph || selectionEndsInParagraphSeperator);
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000270 if (startAndEndInSameUnsplittableElement && previousPositionIsBlankParagraph && hasBlankParagraphAfterEndOrIsEndOfContent) {
271 m_needPlaceholder = false;
272 Position position;
megan_gardner@apple.com35d11892020-06-25 22:25:39 +0000273 if (endPositionIsBlankParagraph)
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000274 position = startOfNextParagraph(startOfNextParagraph(m_downstreamEnd)).deepEquivalent();
275 else
276 position = VisiblePosition(m_downstreamEnd).next().deepEquivalent();
277 m_upstreamEnd = position.upstream();
278 m_downstreamEnd = position.downstream();
darin@apple.comd1d65f92020-09-02 21:01:59 +0000279 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VisiblePosition::defaultAffinity);
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000280 setStartingSelectionOnSmartDelete(m_upstreamStart, m_downstreamEnd);
281 }
282 if (startAndEndInSameUnsplittableElement && selectionEndIsEndOfContent && previousPositionIsBlankParagraph && selectionEndsInParagraphSeperator) {
283 m_needPlaceholder = false;
megan_gardner@apple.comffce9df2020-06-25 20:57:59 +0000284 VisiblePosition endOfParagraphBeforeStart;
285 if (previousPositionIsStartOfContent)
286 endOfParagraphBeforeStart = endOfParagraph(VisiblePosition { m_upstreamStart }.previous());
287 else
288 endOfParagraphBeforeStart = endOfParagraph(VisiblePosition { m_upstreamStart }.previous().previous());
289 auto position = endOfParagraphBeforeStart.deepEquivalent();
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000290 m_upstreamStart = position.upstream();
291 m_downstreamStart = position.downstream();
darin@apple.comc8d8b552020-09-03 21:38:50 +0000292 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(Affinity::Downstream);
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000293 setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd);
294 }
295}
296
wenson_hsieh@apple.com7cd854e2017-08-24 04:12:55 +0000297bool DeleteSelectionCommand::initializePositionData()
mjsa0fe4042005-05-13 08:37:15 +0000298{
justing113aaf32007-01-25 01:00:36 +0000299 Position start, end;
300 initializeStartEnd(start, end);
301
jiewen_tan@apple.com8a64a4a2015-11-05 00:46:34 +0000302 if (!isEditablePosition(start, ContentIsEditable))
303 start = firstEditablePositionAfterPositionInRoot(start, highestEditableRoot(start));
304 if (!isEditablePosition(end, ContentIsEditable))
305 end = lastEditablePositionBeforePositionInRoot(end, highestEditableRoot(start));
306
wenson_hsieh@apple.com7cd854e2017-08-24 04:12:55 +0000307 if (start.isNull() || end.isNull())
308 return false;
309
justing113aaf32007-01-25 01:00:36 +0000310 m_upstreamStart = start.upstream();
311 m_downstreamStart = start.downstream();
312 m_upstreamEnd = end.upstream();
313 m_downstreamEnd = end.downstream();
harrisondec5e092006-01-25 21:05:02 +0000314
justing06a653c2007-04-04 20:49:52 +0000315 m_startRoot = editableRootForPosition(start);
316 m_endRoot = editableRootForPosition(end);
317
justin.garcia@apple.comdeed90d2007-12-13 21:32:12 +0000318 m_startTableRow = enclosingNodeOfType(start, &isTableRow);
319 m_endTableRow = enclosingNodeOfType(end, &isTableRow);
justing187d2922007-07-17 21:05:21 +0000320
justingb7701502007-03-29 22:34:28 +0000321 // Don't move content out of a table cell.
justin.garcia@apple.comab87ef82008-05-29 20:37:53 +0000322 // If the cell is non-editable, enclosingNodeOfType won't return it by default, so
323 // tell that function that we don't care if it returns non-editable nodes.
cdumez@apple.come1840be2021-09-20 21:45:40 +0000324 RefPtr startCell { enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary) };
325 RefPtr endCell { enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary) };
justing8f16cee2006-06-22 00:20:29 +0000326 // FIXME: This isn't right. A borderless table with two rows and a single column would appear as two paragraphs.
justingb7701502007-03-29 22:34:28 +0000327 if (endCell && endCell != startCell)
justing8f16cee2006-06-22 00:20:29 +0000328 m_mergeBlocksAfterDelete = false;
329
justing11ec52e2006-06-07 00:09:37 +0000330 // Usually the start and the end of the selection to delete are pulled together as a result of the deletion.
justing93798f12006-06-08 04:04:38 +0000331 // Sometimes they aren't (like when no merge is requested), so we must choose one position to hold the caret
332 // and receive the placeholder after deletion.
justing11ec52e2006-06-07 00:09:37 +0000333 VisiblePosition visibleEnd(m_downstreamEnd);
334 if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd))
335 m_endingPosition = m_downstreamEnd;
336 else
337 m_endingPosition = m_downstreamStart;
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000338
339 // We don't want to merge into a block if it will mean changing the quote level of content after deleting
340 // selections that contain a whole number paragraphs plus a line break, since it is unclear to most users
341 // that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior
342 // for indented paragraphs.
adele@apple.come2bc16b2009-04-28 22:17:29 +0000343 // Only apply this rule if the endingSelection is a range selection. If it is a caret, then other operations have created
344 // 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.
345 if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end)
346 && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start))
347 && endingSelection().isRange()) {
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000348 m_mergeBlocksAfterDelete = false;
349 m_pruneStartBlockIfNecessary = true;
350 }
justing8f16cee2006-06-22 00:20:29 +0000351
mjsa0fe4042005-05-13 08:37:15 +0000352 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
darin9aa45a42006-01-15 23:32:02 +0000353 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity());
darin@apple.comd1d65f92020-09-02 21:01:59 +0000354 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VisiblePosition::defaultAffinity);
mjsa0fe4042005-05-13 08:37:15 +0000355
356 if (m_smartDelete) {
357
358 // skip smart delete if the selection to delete already starts or ends with whitespace
darin9aa45a42006-01-15 23:32:02 +0000359 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.affinity()).deepEquivalent();
darin@apple.comd1d65f92020-09-02 21:01:59 +0000360 bool skipSmartDelete = pos.trailingWhitespacePosition(VisiblePosition::defaultAffinity, true).isNotNull();
mjsa0fe4042005-05-13 08:37:15 +0000361 if (!skipSmartDelete)
darin@apple.comd1d65f92020-09-02 21:01:59 +0000362 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VisiblePosition::defaultAffinity, true).isNotNull();
mjsa0fe4042005-05-13 08:37:15 +0000363
364 // extend selection upstream if there is whitespace there
darin9aa45a42006-01-15 23:32:02 +0000365 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.affinity(), true).isNotNull();
mjsa0fe4042005-05-13 08:37:15 +0000366 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
darin@apple.comd1d65f92020-09-02 21:01:59 +0000367 auto visiblePos = VisiblePosition(m_upstreamStart).previous();
mjsa0fe4042005-05-13 08:37:15 +0000368 pos = visiblePos.deepEquivalent();
369 // Expand out one character upstream for smart delete and recalculate
370 // positions based on this change.
371 m_upstreamStart = pos.upstream();
372 m_downstreamStart = pos.downstream();
373 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
ojan@chromium.org77dae9d2010-03-09 23:41:38 +0000374
375 setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd);
mjsa0fe4042005-05-13 08:37:15 +0000376 }
377
378 // trailing whitespace is only considered for smart delete if there is no leading
379 // whitespace, as in the case where you double-click the first word of a paragraph.
darin@apple.comd1d65f92020-09-02 21:01:59 +0000380 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VisiblePosition::defaultAffinity, true).isNotNull()) {
mjsa0fe4042005-05-13 08:37:15 +0000381 // Expand out one character downstream for smart delete and recalculate
382 // positions based on this change.
darin@apple.comd1d65f92020-09-02 21:01:59 +0000383 pos = VisiblePosition(m_downstreamEnd).next().deepEquivalent();
mjsa0fe4042005-05-13 08:37:15 +0000384 m_upstreamEnd = pos.upstream();
385 m_downstreamEnd = pos.downstream();
darin@apple.comd1d65f92020-09-02 21:01:59 +0000386 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VisiblePosition::defaultAffinity);
ojan@chromium.org77dae9d2010-03-09 23:41:38 +0000387
388 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd);
mjsa0fe4042005-05-13 08:37:15 +0000389 }
megan_gardner@apple.com01af70af2019-03-21 17:03:17 +0000390
391 if (shouldSmartDeleteParagraphSpacers())
392 smartDeleteParagraphSpacers();
mjsa0fe4042005-05-13 08:37:15 +0000393 }
394
commit-queue@webkit.orgc56f5de2011-01-19 09:32:09 +0000395 // We must pass call parentAnchoredEquivalent on the positions since some editing positions
justin.garcia@apple.come1da2732008-06-25 21:22:12 +0000396 // that appear inside their nodes aren't really inside them. [hr, 0] is one example.
commit-queue@webkit.orgc56f5de2011-01-19 09:32:09 +0000397 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing element getters
justin.garcia@apple.come1da2732008-06-25 21:22:12 +0000398 // like the one below, since editing functions should obviously accept editing positions.
399 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable
400 // node. This was done to match existing behavior, but it seems wrong.
rniwa@webkit.org45ecbaf2011-03-11 00:52:53 +0000401 m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
402 m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary);
wenson_hsieh@apple.com7cd854e2017-08-24 04:12:55 +0000403
404 return true;
mjsa0fe4042005-05-13 08:37:15 +0000405}
406
407void DeleteSelectionCommand::saveTypingStyleState()
408{
justin.garcia@apple.com64125512008-02-20 17:48:44 +0000409 // A common case is deleting characters that are all from the same text node. In
410 // that case, the style at the start of the selection before deletion will be the
411 // same as the style at the start of the selection after deletion (since those
412 // two positions will be identical). Therefore there is no need to save the
413 // typing style at the start of the selection, nor is there a reason to
414 // compute the style at the start of the selection after deletion (see the
415 // early return in calculateTypingStyleAfterDelete).
a.bah@samsung.com305f6852013-09-10 07:27:21 +0000416 // However, if typing style was previously set from another text node at the previous
417 // position (now deleted), we need to clear that style as well.
418 if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() && m_upstreamStart.deprecatedNode()->isTextNode()) {
shihchieh_lee@apple.com501b37c2020-04-28 16:36:38 +0000419 document().selection().clearTypingStyle();
justin.garcia@apple.com64125512008-02-20 17:48:44 +0000420 return;
a.bah@samsung.com305f6852013-09-10 07:27:21 +0000421 }
rniwa@webkit.org1de555e2009-08-07 21:36:24 +0000422
mjsa0fe4042005-05-13 08:37:15 +0000423 // Figure out the typing style in effect before the delete is done.
commit-queue@webkit.orgecadd602013-11-07 03:49:36 +0000424 m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), EditingStyle::EditingPropertiesInEffect);
rniwa@webkit.orgb70a5772011-09-22 19:25:54 +0000425 m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDelete.start()));
rniwa@webkit.org1de555e2009-08-07 21:36:24 +0000426
justing503ccf12005-07-29 22:50:47 +0000427 // If we're deleting into a Mail blockquote, save the style at end() instead of start()
428 // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
rniwa@webkit.org24bd1d32011-03-17 00:03:22 +0000429 if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote))
rniwa@webkit.org7e06f4a2010-11-06 21:19:59 +0000430 m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end());
rniwa@webkit.org1de555e2009-08-07 21:36:24 +0000431 else
cdumez@apple.comd839ea12015-07-04 19:42:18 +0000432 m_deleteIntoBlockquoteStyle = nullptr;
mjsa0fe4042005-05-13 08:37:15 +0000433}
434
435bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
436{
cdumez@apple.come1840be2021-09-20 21:45:40 +0000437 RefPtr nodeAfterUpstreamStart { m_upstreamStart.computeNodeAfterPosition() };
438 RefPtr nodeAfterDownstreamStart { m_downstreamStart.computeNodeAfterPosition() };
rniwa@webkit.org3810eff2012-06-27 00:00:52 +0000439 // Upstream end will appear before BR due to canonicalization
cdumez@apple.come1840be2021-09-20 21:45:40 +0000440 RefPtr nodeAfterUpstreamEnd { m_upstreamEnd.computeNodeAfterPosition() };
rniwa@webkit.org3810eff2012-06-27 00:00:52 +0000441
442 if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart)
443 return false;
444
mjsa0fe4042005-05-13 08:37:15 +0000445 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
rniwa@webkit.org3810eff2012-06-27 00:00:52 +0000446 bool upstreamStartIsBR = nodeAfterUpstreamStart->hasTagName(brTag);
447 bool downstreamStartIsBR = nodeAfterDownstreamStart->hasTagName(brTag);
enrica@apple.comd2ba8152013-07-09 02:48:19 +0000448 // We should consider that the BR is on a line by itself also when we have <br><br>. This test should be true only
449 // when the two elements are siblings and should be false in a case like <div><br></div><br>.
450 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && ((nodeAfterDownstreamStart == nodeAfterUpstreamEnd) || (nodeAfterUpstreamEnd && nodeAfterUpstreamEnd->hasTagName(brTag) && nodeAfterUpstreamStart->nextSibling() == nodeAfterUpstreamEnd));
451
mjsa0fe4042005-05-13 08:37:15 +0000452 if (isBROnLineByItself) {
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000453 removeNode(*nodeAfterDownstreamStart);
mjsa0fe4042005-05-13 08:37:15 +0000454 return true;
455 }
456
rniwa@webkit.org3810eff2012-06-27 00:00:52 +0000457 // FIXME: This code doesn't belong in here.
458 // We detect the case where the start is an empty line consisting of BR not wrapped in a block element.
a.bah@samsung.comb0247012013-08-23 11:38:26 +0000459 if (upstreamStartIsBR && downstreamStartIsBR
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000460 && !(isStartOfBlock(positionBeforeNode(nodeAfterUpstreamStart.get())) && isEndOfBlock(positionAfterNode(nodeAfterDownstreamStart.get())))
a.bah@samsung.comb0247012013-08-23 11:38:26 +0000461 && (!nodeAfterUpstreamEnd || nodeAfterUpstreamEnd->hasTagName(brTag) || nodeAfterUpstreamEnd->previousSibling() != nodeAfterUpstreamStart)) {
mitz@apple.comfc292c32009-05-03 04:09:41 +0000462 m_startsAtEmptyLine = true;
harrison581c07b2006-11-06 18:08:52 +0000463 m_endingPosition = m_downstreamEnd;
464 }
465
mjsa0fe4042005-05-13 08:37:15 +0000466 return false;
467}
468
kalman@chromium.org4cb651d2011-03-31 07:38:10 +0000469static Position firstEditablePositionInNode(Node* node)
470{
471 ASSERT(node);
cdumez@apple.come1840be2021-09-20 21:45:40 +0000472 RefPtr next { node };
antti@apple.comf02be1e2013-12-21 18:51:04 +0000473 while (next && !next->hasEditableStyle())
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000474 next = NodeTraversal::next(*next, node);
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000475 return next ? firstPositionInOrBeforeNode(next.get()) : Position();
kalman@chromium.org4cb651d2011-03-31 07:38:10 +0000476}
477
zalan@apple.com53862672017-02-10 22:36:12 +0000478void DeleteSelectionCommand::insertBlockPlaceholderForTableCellIfNeeded(Element& element)
479{
480 // Make sure empty cell has some height.
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000481 {
482 ScriptDisallowedScope scriptDisallowedScope;
483 auto* renderer = element.renderer();
484 if (!is<RenderTableCell>(renderer))
485 return;
486 if (downcast<RenderTableCell>(*renderer).contentHeight() > 0)
487 return;
488 }
zalan@apple.com53862672017-02-10 22:36:12 +0000489 insertBlockPlaceholder(firstEditablePositionInNode(&element));
490}
491
492void DeleteSelectionCommand::removeNodeUpdatingStates(Node& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
493{
commit-queue@webkit.orga58a7fb2021-09-07 08:11:28 +0000494 if (&node == m_startBlock) {
495 auto prev = VisiblePosition(firstPositionInNode(m_startBlock.get())).previous();
496 if (!prev.isNull() && !isEndOfBlock(prev))
497 m_needPlaceholder = true;
498 } else if (&node == m_endBlock) {
499 auto next = VisiblePosition(lastPositionInNode(m_endBlock.get())).next();
500 if (!next.isNull() && !isStartOfBlock(next))
501 m_needPlaceholder = true;
502 }
zalan@apple.com53862672017-02-10 22:36:12 +0000503
504 // FIXME: Update the endpoints of the range being deleted.
505 updatePositionForNodeRemoval(m_endingPosition, node);
506 updatePositionForNodeRemoval(m_leadingWhitespace, node);
507 updatePositionForNodeRemoval(m_trailingWhitespace, node);
508
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000509 CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable);
zalan@apple.com53862672017-02-10 22:36:12 +0000510}
511
512static inline bool shouldRemoveContentOnly(const Node& node)
513{
514 return isTableStructureNode(&node) || node.isRootEditableElement();
515}
516
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000517void DeleteSelectionCommand::removeNode(Node& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
justing11ec52e2006-06-07 00:09:37 +0000518{
commit-queue@webkit.org7290dd92021-05-07 21:54:02 +0000519 if (!node.parentNode())
520 return;
521
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000522 Ref<Node> protectedNode = node;
523 if (m_startRoot != m_endRoot && !(node.isDescendantOf(m_startRoot.get()) && node.isDescendantOf(m_endRoot.get()))) {
justing06a653c2007-04-04 20:49:52 +0000524 // If a node is not in both the start and end editable roots, remove it only if its inside an editable region.
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000525 if (!node.parentNode()->hasEditableStyle()) {
justing06a653c2007-04-04 20:49:52 +0000526 // Don't remove non-editable atomic nodes.
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000527 if (!node.firstChild())
justing06a653c2007-04-04 20:49:52 +0000528 return;
529 // Search this non-editable region for editable regions to empty.
cdumez@apple.come1840be2021-09-20 21:45:40 +0000530 RefPtr child { node.firstChild() };
justing06a653c2007-04-04 20:49:52 +0000531 while (child) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000532 RefPtr nextChild { child->nextSibling() };
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000533 removeNode(*child, shouldAssumeContentIsAlwaysEditable);
justing06a653c2007-04-04 20:49:52 +0000534 // Bail if nextChild is no longer node's child.
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000535 if (nextChild && nextChild->parentNode() != &node)
justing06a653c2007-04-04 20:49:52 +0000536 return;
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000537 child = WTFMove(nextChild);
justing06a653c2007-04-04 20:49:52 +0000538 }
539
540 // Don't remove editable regions that are inside non-editable ones, just clear them.
541 return;
542 }
543 }
544
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000545 if (shouldRemoveContentOnly(node)) {
justing11ec52e2006-06-07 00:09:37 +0000546 // Do not remove an element of table structure; remove its contents.
547 // Likewise for the root editable element.
cdumez@apple.come1840be2021-09-20 21:45:40 +0000548 RefPtr child { NodeTraversal::next(node, &node) };
justing11ec52e2006-06-07 00:09:37 +0000549 while (child) {
zalan@apple.com53862672017-02-10 22:36:12 +0000550 if (shouldRemoveContentOnly(*child)) {
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000551 child = NodeTraversal::next(*child, &node);
zalan@apple.com53862672017-02-10 22:36:12 +0000552 continue;
553 }
cdumez@apple.come1840be2021-09-20 21:45:40 +0000554 RefPtr nextChild { NodeTraversal::nextSkippingChildren(*child, &node) };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000555 removeNodeUpdatingStates(*child, shouldAssumeContentIsAlwaysEditable);
556 child = WTFMove(nextChild);
justing11ec52e2006-06-07 00:09:37 +0000557 }
558
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000559 ASSERT(is<Element>(node));
560 auto& element = downcast<Element>(node);
akling@apple.comd455eb62013-09-01 05:30:31 +0000561 document().updateLayoutIgnorePendingStylesheets();
zalan@apple.com53862672017-02-10 22:36:12 +0000562 // Check if we need to insert a placeholder for descendant table cells.
cdumez@apple.come1840be2021-09-20 21:45:40 +0000563 RefPtr descendant { ElementTraversal::next(element, &element) };
zalan@apple.com53862672017-02-10 22:36:12 +0000564 while (descendant) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000565 RefPtr nextDescendant { ElementTraversal::next(*descendant, &element) };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000566 insertBlockPlaceholderForTableCellIfNeeded(*descendant);
567 descendant = WTFMove(nextDescendant);
kalman@chromium.org4cb651d2011-03-31 07:38:10 +0000568 }
zalan@apple.com53862672017-02-10 22:36:12 +0000569 insertBlockPlaceholderForTableCellIfNeeded(element);
justing11ec52e2006-06-07 00:09:37 +0000570 return;
571 }
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000572 removeNodeUpdatingStates(node, shouldAssumeContentIsAlwaysEditable);
justingf81641c2006-06-14 23:23:50 +0000573}
574
andersca@apple.comd8dda092009-01-23 19:52:19 +0000575static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
justingf81641c2006-06-14 23:23:50 +0000576{
rniwa@webkit.org186f5bb2011-01-18 21:33:34 +0000577 if (position.anchorType() != Position::PositionIsOffsetInAnchor || position.containerNode() != node)
578 return;
579
580 if (position.offsetInContainerNode() > offset + count)
581 position.moveToOffset(position.offsetInContainerNode() - count);
582 else if (position.offsetInContainerNode() > offset)
583 position.moveToOffset(offset);
justing11ec52e2006-06-07 00:09:37 +0000584}
585
cdumez@apple.comecdce032017-05-03 18:41:47 +0000586void DeleteSelectionCommand::deleteTextFromNode(Text& node, unsigned offset, unsigned count)
justing11ec52e2006-06-07 00:09:37 +0000587{
justingf81641c2006-06-14 23:23:50 +0000588 // FIXME: Update the endpoints of the range being deleted.
cdumez@apple.comecdce032017-05-03 18:41:47 +0000589 updatePositionForTextRemoval(&node, offset, count, m_endingPosition);
590 updatePositionForTextRemoval(&node, offset, count, m_leadingWhitespace);
591 updatePositionForTextRemoval(&node, offset, count, m_trailingWhitespace);
592 updatePositionForTextRemoval(&node, offset, count, m_downstreamEnd);
justing11ec52e2006-06-07 00:09:37 +0000593
594 CompositeEditCommand::deleteTextFromNode(node, offset, count);
595}
596
commit-queue@webkit.org72e63482012-08-18 00:31:00 +0000597void DeleteSelectionCommand::makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss()
598{
darin@apple.comea2c7f82020-04-26 15:33:03 +0000599 auto range = m_selectionToDelete.toNormalizedRange();
600 if (!range)
601 return;
602 auto nodes = intersectingNodes(*range).begin();
603 while (nodes) {
cdumez@apple.com6674f142021-09-22 02:55:27 +0000604 Ref node = *nodes;
darin@apple.comea2c7f82020-04-26 15:33:03 +0000605 auto shouldMove = is<HTMLLinkElement>(node)
606 || (is<HTMLStyleElement>(node) && !downcast<HTMLStyleElement>(node.get()).hasAttributeWithoutSynchronization(scopedAttr));
607 if (!shouldMove)
608 nodes.advance();
609 else {
610 nodes.advanceSkippingChildren();
cdumez@apple.come1840be2021-09-20 21:45:40 +0000611 if (RefPtr rootEditableElement = node->rootEditableElement()) {
darin@apple.comea2c7f82020-04-26 15:33:03 +0000612 removeNode(node.get());
613 appendNode(node.get(), *rootEditableElement);
rniwa@webkit.orgd14c7ce2013-05-21 03:18:22 +0000614 }
commit-queue@webkit.org72e63482012-08-18 00:31:00 +0000615 }
commit-queue@webkit.org72e63482012-08-18 00:31:00 +0000616 }
617}
618
mjsa0fe4042005-05-13 08:37:15 +0000619void DeleteSelectionCommand::handleGeneralDelete()
620{
rniwa@webkit.org23ae78c2011-10-13 20:58:42 +0000621 if (m_upstreamStart.isNull())
622 return;
623
eric@webkit.org4e32ebc2009-04-30 01:09:57 +0000624 int startOffset = m_upstreamStart.deprecatedEditingOffset();
cdumez@apple.come1840be2021-09-20 21:45:40 +0000625 RefPtr startNode { m_upstreamStart.deprecatedNode() };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000626
commit-queue@webkit.org72e63482012-08-18 00:31:00 +0000627 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss();
rniwa@webkit.org23ae78c2011-10-13 20:58:42 +0000628
lweintraub635ec2a2006-07-13 18:28:28 +0000629 // Never remove the start block unless it's a table, in which case we won't merge content in.
darin@apple.combc63aba2016-05-15 22:08:52 +0000630 if (startNode == m_startBlock && !startOffset && canHaveChildrenForEditing(*startNode) && !is<HTMLTableElement>(*startNode)) {
mjsa0fe4042005-05-13 08:37:15 +0000631 startOffset = 0;
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000632 startNode = NodeTraversal::next(*startNode);
rniwa@webkit.org4df07a22012-01-31 20:41:05 +0000633 if (!startNode)
634 return;
justinge6f80c12006-06-09 04:57:51 +0000635 }
636
darin@apple.comd385be42016-05-14 20:09:50 +0000637 int startNodeCaretMaxOffset = caretMaxOffset(*startNode);
638 if (startOffset >= startNodeCaretMaxOffset && is<Text>(*startNode)) {
cdumez@apple.coma38df4d2014-09-29 17:20:18 +0000639 Text& text = downcast<Text>(*startNode);
darin@apple.comd385be42016-05-14 20:09:50 +0000640 if (text.length() > static_cast<unsigned>(startNodeCaretMaxOffset))
cdumez@apple.comecdce032017-05-03 18:41:47 +0000641 deleteTextFromNode(text, startNodeCaretMaxOffset, text.length() - startNodeCaretMaxOffset);
justinge6f80c12006-06-09 04:57:51 +0000642 }
643
darin@apple.comd385be42016-05-14 20:09:50 +0000644 if (startOffset >= lastOffsetForEditing(*startNode)) {
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000645 startNode = NodeTraversal::nextSkippingChildren(*startNode);
mjsa0fe4042005-05-13 08:37:15 +0000646 startOffset = 0;
647 }
648
649 // Done adjusting the start. See if we're all done.
justing11ec52e2006-06-07 00:09:37 +0000650 if (!startNode)
mjsa0fe4042005-05-13 08:37:15 +0000651 return;
652
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000653 if (startNode == m_downstreamEnd.deprecatedNode()) {
commit-queue@webkit.org2aa8d032010-09-22 20:43:26 +0000654 if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) {
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000655 if (is<Text>(*startNode)) {
mjsa0fe4042005-05-13 08:37:15 +0000656 // in a text node that needs to be trimmed
cdumez@apple.coma38df4d2014-09-29 17:20:18 +0000657 Text& text = downcast<Text>(*startNode);
cdumez@apple.comecdce032017-05-03 18:41:47 +0000658 deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset);
mjsa0fe4042005-05-13 08:37:15 +0000659 } else {
cdumez@apple.comecdce032017-05-03 18:41:47 +0000660 removeChildrenInRange(*startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset());
mjsa0fe4042005-05-13 08:37:15 +0000661 m_endingPosition = m_upstreamStart;
662 }
663 }
commit-queue@webkit.org2aa8d032010-09-22 20:43:26 +0000664
665 // The selection to delete is all in one node.
666 if (!startNode->renderer() || (!startOffset && m_downstreamEnd.atLastEditingPositionForNode()))
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000667 removeNode(*startNode);
mjsa0fe4042005-05-13 08:37:15 +0000668 }
669 else {
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000670 bool startNodeWasDescendantOfEndNode = m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode());
mjsa0fe4042005-05-13 08:37:15 +0000671 // The selection to delete spans more than one node.
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000672 auto node = startNode.copyRef();
mjsa0fe4042005-05-13 08:37:15 +0000673
674 if (startOffset > 0) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000675 if (is<Text>(*node)) {
mjsa0fe4042005-05-13 08:37:15 +0000676 // in a text node that needs to be trimmed
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000677 Text& text = downcast<Text>(*startNode);
cdumez@apple.comecdce032017-05-03 18:41:47 +0000678 deleteTextFromNode(text, startOffset, text.length() - startOffset);
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000679 node = NodeTraversal::next(*startNode);
mjsa0fe4042005-05-13 08:37:15 +0000680 } else {
cdumez@apple.comd9975172014-09-17 02:00:40 +0000681 node = startNode->traverseToChildAt(startOffset);
mjsa0fe4042005-05-13 08:37:15 +0000682 }
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000683 } else if (startNode == m_upstreamEnd.deprecatedNode() && is<Text>(*startNode)) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000684 Text& text = downcast<Text>(*startNode);
cdumez@apple.comecdce032017-05-03 18:41:47 +0000685 deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset());
mjsa0fe4042005-05-13 08:37:15 +0000686 }
687
688 // handle deleting all nodes that are completely selected
rniwa@webkit.orgfa66f472021-03-04 01:48:53 +0000689 while (node && node != m_downstreamEnd.deprecatedNode() && !m_downstreamEnd.isNull() && !m_downstreamEnd.isOrphan()) {
darin@apple.comd1d65f92020-09-02 21:01:59 +0000690 if (firstPositionInOrBeforeNode(node.get()) >= m_downstreamEnd) {
antti@apple.comb498fe82012-12-12 03:12:23 +0000691 // NodeTraversal::nextSkippingChildren just blew past the end position, so stop deleting
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000692 node = nullptr;
cdumez@apple.comca12b502016-11-17 06:24:13 +0000693 } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(*node)) {
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000694 RefPtr<Node> nextNode = NodeTraversal::nextSkippingChildren(*node);
mjsa0fe4042005-05-13 08:37:15 +0000695 // if we just removed a node from the end container, update end position so the
696 // check above will work
cdumez@apple.com2cbdf512014-11-11 00:02:57 +0000697 updatePositionForNodeRemoval(m_downstreamEnd, *node);
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000698 removeNode(*node);
justing24754322007-07-09 20:27:05 +0000699 node = nextNode.get();
mjsa0fe4042005-05-13 08:37:15 +0000700 } else {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000701 RefPtr lastDescendant { node->lastDescendant() };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000702 if (m_downstreamEnd.deprecatedNode() == lastDescendant && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(*lastDescendant)) {
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000703 removeNode(*node);
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000704 node = nullptr;
justing95561262006-06-21 21:22:50 +0000705 } else
cdumez@apple.comb7f48502015-01-26 22:36:36 +0000706 node = NodeTraversal::next(*node);
mjsa0fe4042005-05-13 08:37:15 +0000707 }
708 }
mjsa0fe4042005-05-13 08:37:15 +0000709
rniwa@webkit.org1484cab2019-06-22 04:04:23 +0000710 if (!m_downstreamEnd.isNull() && !m_downstreamEnd.isOrphan() && m_downstreamEnd.deprecatedNode() != startNode
711 && !m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())
712 && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(*m_downstreamEnd.deprecatedNode())) {
darin@apple.combc63aba2016-05-15 22:08:52 +0000713 if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(*m_downstreamEnd.deprecatedNode())) {
justing395a18e2007-06-26 06:52:15 +0000714 // The node itself is fully selected, not just its contents. Delete it.
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000715 removeNode(*m_downstreamEnd.deprecatedNode());
mjsa0fe4042005-05-13 08:37:15 +0000716 } else {
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000717 if (is<Text>(*m_downstreamEnd.deprecatedNode())) {
mjsa0fe4042005-05-13 08:37:15 +0000718 // in a text node that needs to be trimmed
cdumez@apple.coma38df4d2014-09-29 17:20:18 +0000719 Text& text = downcast<Text>(*m_downstreamEnd.deprecatedNode());
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000720 if (m_downstreamEnd.deprecatedEditingOffset() > 0)
cdumez@apple.comecdce032017-05-03 18:41:47 +0000721 deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset());
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000722 // Remove children of m_downstreamEnd.deprecatedNode() that come after m_upstreamStart.
723 // Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.deprecatedNode()
justingd197d6b2007-08-25 01:20:33 +0000724 // and m_upstreamStart has been removed from the document, because then we don't
725 // know how many children to remove.
726 // FIXME: Make m_upstreamStart a position we update as we remove content, then we can
727 // always know which children to remove.
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000728 } else if (!(startNodeWasDescendantOfEndNode && !m_upstreamStart.anchorNode()->isConnected())) {
cdumez@apple.com37510702014-09-16 18:28:57 +0000729 unsigned offset = 0;
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000730 if (m_upstreamStart.deprecatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode())) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000731 RefPtr n { m_upstreamStart.deprecatedNode() };
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000732 while (n && n->parentNode() != m_downstreamEnd.deprecatedNode())
mjsa0fe4042005-05-13 08:37:15 +0000733 n = n->parentNode();
734 if (n)
cdumez@apple.com37510702014-09-16 18:28:57 +0000735 offset = n->computeNodeIndex() + 1;
mjsa0fe4042005-05-13 08:37:15 +0000736 }
cdumez@apple.comecdce032017-05-03 18:41:47 +0000737 removeChildrenInRange(*m_downstreamEnd.deprecatedNode(), offset, m_downstreamEnd.deprecatedEditingOffset());
darin@apple.come48d2732020-09-21 20:28:24 +0000738 m_downstreamEnd = makeDeprecatedLegacyPosition(m_downstreamEnd.deprecatedNode(), offset);
mjsa0fe4042005-05-13 08:37:15 +0000739 }
740 }
741 }
742 }
743}
744
mjsa0fe4042005-05-13 08:37:15 +0000745void DeleteSelectionCommand::fixupWhitespace()
746{
akling@apple.comd455eb62013-09-01 05:30:31 +0000747 document().updateLayoutIgnorePendingStylesheets();
justingf81641c2006-06-14 23:23:50 +0000748 // FIXME: isRenderedCharacter should be removed, and we should use VisiblePosition::characterAfter and VisiblePosition::characterBefore
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000749 if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter() && is<Text>(*m_leadingWhitespace.deprecatedNode())) {
cdumez@apple.coma38df4d2014-09-29 17:20:18 +0000750 Text& textNode = downcast<Text>(*m_leadingWhitespace.deprecatedNode());
751 ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace());
cdumez@apple.comecdce032017-05-03 18:41:47 +0000752 replaceTextInNodePreservingMarkers(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
mjsa0fe4042005-05-13 08:37:15 +0000753 }
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000754 if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter() && is<Text>(*m_trailingWhitespace.deprecatedNode())) {
cdumez@apple.coma38df4d2014-09-29 17:20:18 +0000755 Text& textNode = downcast<Text>(*m_trailingWhitespace.deprecatedNode());
756 ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace());
cdumez@apple.comecdce032017-05-03 18:41:47 +0000757 replaceTextInNodePreservingMarkers(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
mjsa0fe4042005-05-13 08:37:15 +0000758 }
759}
760
justingf81641c2006-06-14 23:23:50 +0000761// If a selection starts in one block and ends in another, we have to merge to bring content before the
762// start together with content after the end.
justing217ded82006-04-04 20:52:10 +0000763void DeleteSelectionCommand::mergeParagraphs()
mjsa0fe4042005-05-13 08:37:15 +0000764{
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000765 if (!m_mergeBlocksAfterDelete) {
766 if (m_pruneStartBlockIfNecessary) {
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000767 // We aren't going to merge into the start block, so remove it if it's empty.
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000768 prune(m_startBlock.get());
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000769 // Removing the start block during a deletion is usually an indication that we need
770 // a placeholder, but not in this case.
771 m_needPlaceholder = false;
772 }
mjsa0fe4042005-05-13 08:37:15 +0000773 return;
justin.garcia@apple.com7e95c7b2008-11-12 20:08:06 +0000774 }
775
776 // It shouldn't have been asked to both try and merge content into the start block and prune it.
777 ASSERT(!m_pruneStartBlockIfNecessary);
mjsa0fe4042005-05-13 08:37:15 +0000778
justing217ded82006-04-04 20:52:10 +0000779 // FIXME: Deletion should adjust selection endpoints as it removes nodes so that we never get into this state (4099839).
darin@apple.comd1d65f92020-09-02 21:01:59 +0000780 if (m_downstreamEnd.isNull() || m_upstreamStart.isNull() || m_downstreamEnd.isOrphan() || m_upstreamStart.isOrphan())
781 return;
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000782
darin@apple.comd1d65f92020-09-02 21:01:59 +0000783 if (m_upstreamStart >= m_downstreamEnd)
justingced67722006-05-01 21:43:03 +0000784 return;
darin@apple.comd1d65f92020-09-02 21:01:59 +0000785
justing217ded82006-04-04 20:52:10 +0000786 VisiblePosition startOfParagraphToMove(m_downstreamEnd);
787 VisiblePosition mergeDestination(m_upstreamStart);
788
justin.garcia@apple.com60777c92007-11-14 22:33:07 +0000789 // m_downstreamEnd's block has been emptied out by deletion. There is no content inside of it to
790 // move, so just remove it.
cdumez@apple.come1840be2021-09-20 21:45:40 +0000791 RefPtr endBlock { enclosingBlock(m_downstreamEnd.deprecatedNode()) };
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000792 if (!endBlock)
793 return;
794
795 if (!endBlock->contains(startOfParagraphToMove.deepEquivalent().deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode()) {
796 removeNode(*endBlock);
justin.garcia@apple.com60777c92007-11-14 22:33:07 +0000797 return;
798 }
799
justingced67722006-05-01 21:43:03 +0000800 // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
leviw@chromium.org688f6412011-03-14 21:19:29 +0000801 if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) || m_startsAtEmptyLine) {
cdumez@apple.comaac2e342017-05-02 17:41:46 +0000802 insertNodeAt(HTMLBRElement::create(document()), m_upstreamStart);
justingced67722006-05-01 21:43:03 +0000803 mergeDestination = VisiblePosition(m_upstreamStart);
804 }
harrisondec5e092006-01-25 21:05:02 +0000805
justingced67722006-05-01 21:43:03 +0000806 if (mergeDestination == startOfParagraphToMove)
mjsa0fe4042005-05-13 08:37:15 +0000807 return;
justing217ded82006-04-04 20:52:10 +0000808
commit-queue@webkit.org10809db2013-10-24 19:18:30 +0000809 VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove, CanSkipOverEditingBoundary);
justing217ded82006-04-04 20:52:10 +0000810
darin2b0ff522006-05-03 19:32:21 +0000811 if (mergeDestination == endOfParagraphToMove)
812 return;
mjsa0fe4042005-05-13 08:37:15 +0000813
justinge809f932006-06-23 22:36:12 +0000814 // The rule for merging into an empty block is: only do so if its farther to the right.
815 // FIXME: Consider RTL.
mitz@apple.comfc292c32009-05-03 04:09:41 +0000816 if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000817 if (mergeDestination.deepEquivalent().downstream().deprecatedNode()->hasTagName(brTag)) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000818 RefPtr nodeToRemove { mergeDestination.deepEquivalent().downstream().deprecatedNode() };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000819 removeNodeAndPruneAncestors(*nodeToRemove);
enrica@apple.com25b91e32010-01-11 18:04:58 +0000820 m_endingPosition = startOfParagraphToMove.deepEquivalent();
821 return;
822 }
justinge809f932006-06-23 22:36:12 +0000823 }
824
justin.garcia@apple.com619526a2009-11-05 05:25:24 +0000825 // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination. If there is
826 // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the caret to just before the selection we deleted.
827 // See https://bugs.webkit.org/show_bug.cgi?id=25439
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000828 if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalent().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) {
justin.garcia@apple.com619526a2009-11-05 05:25:24 +0000829 m_endingPosition = m_upstreamStart;
830 return;
831 }
832
darin@apple.com5d3decc2020-08-01 15:50:36 +0000833 auto range = makeSimpleRange(startOfParagraphToMove, endOfParagraphToMove);
834 if (!range)
835 return;
836 auto rangeToBeReplaced = makeSimpleRange(mergeDestination);
837 if (!rangeToBeReplaced)
838 return;
839 if (!document().editor().client()->shouldMoveRangeAfterDelete(*range, *rangeToBeReplaced))
justingf90e28d2007-07-30 18:25:19 +0000840 return;
darin@apple.comea4947c2020-07-22 04:45:40 +0000841
justin.garcia@apple.com60777c92007-11-14 22:33:07 +0000842 // moveParagraphs will insert placeholders if it removes blocks that would require their use, don't let block
843 // removals that it does cause the insertion of *another* placeholder.
844 bool needPlaceholder = m_needPlaceholder;
rniwa@webkit.orgd34b7482010-08-12 02:05:11 +0000845 bool paragraphToMergeIsEmpty = (startOfParagraphToMove == endOfParagraphToMove);
846 moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, false, !paragraphToMergeIsEmpty);
justin.garcia@apple.com60777c92007-11-14 22:33:07 +0000847 m_needPlaceholder = needPlaceholder;
justing93798f12006-06-08 04:04:38 +0000848 // The endingPosition was likely clobbered by the move, so recompute it (moveParagraph selects the moved paragraph).
shihchieh_lee@apple.com87885902020-05-14 00:21:50 +0000849
850 // FIXME (Bug 211793): endingSelection() becomes disconnected in moveParagraph
commit-queue@webkit.orgbb107d22020-11-17 16:29:25 +0000851 if (auto* anchorNode = endingSelection().start().anchorNode(); anchorNode && anchorNode->isConnected())
shihchieh_lee@apple.com87885902020-05-14 00:21:50 +0000852 m_endingPosition = endingSelection().start();
mjsa0fe4042005-05-13 08:37:15 +0000853}
854
justing187d2922007-07-17 21:05:21 +0000855void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows()
856{
commit-queue@webkit.org33902322021-05-07 08:08:40 +0000857 // DeleteSelectionCommand::removeNode does not remove rows but only empties them in preparation for this function.
858 // Instead, DeleteSelectionCommand::removeNodeUpdatingStates is used below, which calls a raw CompositeEditCommand::removeNode and adjusts selection.
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000859 if (m_endTableRow && m_endTableRow->isConnected() && m_endTableRow != m_startTableRow) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000860 RefPtr row { m_endTableRow->previousSibling() };
justin.garcia@apple.com99a05542007-12-13 06:56:28 +0000861 while (row && row != m_startTableRow) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000862 RefPtr previousRow { row->previousSibling() };
commit-queue@webkit.org33902322021-05-07 08:08:40 +0000863 if (isTableRowEmpty(row.get()))
864 removeNodeUpdatingStates(*row, DoNotAssumeContentIsAlwaysEditable);
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000865 row = WTFMove(previousRow);
justing187d2922007-07-17 21:05:21 +0000866 }
867 }
868
justing7d4a4342007-10-13 00:40:48 +0000869 // Remove empty rows after the start row.
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000870 if (m_startTableRow && m_startTableRow->isConnected() && m_startTableRow != m_endTableRow) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000871 RefPtr row { m_startTableRow->nextSibling() };
justin.garcia@apple.com99a05542007-12-13 06:56:28 +0000872 while (row && row != m_endTableRow) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000873 RefPtr nextRow { row->nextSibling() };
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000874 if (isTableRowEmpty(row.get()))
commit-queue@webkit.org33902322021-05-07 08:08:40 +0000875 removeNodeUpdatingStates(*row, DoNotAssumeContentIsAlwaysEditable);
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000876 row = WTFMove(nextRow);
justing187d2922007-07-17 21:05:21 +0000877 }
878 }
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000879
880 if (m_endTableRow && m_endTableRow->isConnected() && m_endTableRow != m_startTableRow) {
justin.garcia@apple.com99a05542007-12-13 06:56:28 +0000881 if (isTableRowEmpty(m_endTableRow.get())) {
882 // Don't remove m_endTableRow if it's where we're putting the ending selection.
cdumez@apple.comca12b502016-11-17 06:24:13 +0000883 if (!m_endingPosition.deprecatedNode()->isDescendantOf(*m_endTableRow)) {
justin.garcia@apple.com99a05542007-12-13 06:56:28 +0000884 // FIXME: We probably shouldn't remove m_endTableRow unless it's fully selected, even if it is empty.
885 // We'll need to start adjusting the selection endpoints during deletion to know whether or not m_endTableRow
886 // was fully selected here.
svillar@igalia.com9a8754f2021-02-12 11:29:32 +0000887 removeNodeUpdatingStates(*m_endTableRow, DoNotAssumeContentIsAlwaysEditable);
justin.garcia@apple.com99a05542007-12-13 06:56:28 +0000888 }
889 }
cdumez@apple.com85cc2622017-02-02 21:29:15 +0000890 }
justing187d2922007-07-17 21:05:21 +0000891}
892
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +0000893void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
mjsa0fe4042005-05-13 08:37:15 +0000894{
justin.garcia@apple.com64125512008-02-20 17:48:44 +0000895 if (!m_typingStyle)
896 return;
897
mjsa0fe4042005-05-13 08:37:15 +0000898 // Compute the difference between the style before the delete and the style now
mjs810e9762006-01-09 21:39:14 +0000899 // after the delete has been done. Set this style on the frame, so other editing
mjsa0fe4042005-05-13 08:37:15 +0000900 // commands being composed with this one will work, and also cache it on the command,
mjs810e9762006-01-09 21:39:14 +0000901 // so the Frame::appliedEditing can set it after the whole composite command
mjsa0fe4042005-05-13 08:37:15 +0000902 // has completed.
justing503ccf12005-07-29 22:50:47 +0000903
eseidelef508982006-01-03 09:19:17 +0000904 // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style
rniwa@webkit.org24bd1d32011-03-17 00:03:22 +0000905 if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary))
eseidelef508982006-01-03 09:19:17 +0000906 m_typingStyle = m_deleteIntoBlockquoteStyle;
cdumez@apple.comd839ea12015-07-04 19:42:18 +0000907 m_deleteIntoBlockquoteStyle = nullptr;
rniwa@webkit.org1de555e2009-08-07 21:36:24 +0000908
rniwa@webkit.org7e06f4a2010-11-06 21:19:59 +0000909 m_typingStyle->prepareToApplyAt(m_endingPosition);
910 if (m_typingStyle->isEmpty())
cdumez@apple.comd839ea12015-07-04 19:42:18 +0000911 m_typingStyle = nullptr;
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +0000912 // This is where we've deleted all traces of a style but not a whole paragraph (that's handled above).
913 // In this case if we start typing, the new characters should have the same style as the just deleted ones,
914 // but, if we change the selection, come back and start typing that style should be lost. Also see
915 // preserveTypingStyle() below.
shihchieh_lee@apple.com501b37c2020-04-28 16:36:38 +0000916 document().selection().setTypingStyle(m_typingStyle.copyRef());
mjsa0fe4042005-05-13 08:37:15 +0000917}
918
919void DeleteSelectionCommand::clearTransientState()
920{
eric@webkit.org87ea95c2009-02-09 21:43:24 +0000921 m_selectionToDelete = VisibleSelection();
mjsa0fe4042005-05-13 08:37:15 +0000922 m_upstreamStart.clear();
923 m_downstreamStart.clear();
924 m_upstreamEnd.clear();
925 m_downstreamEnd.clear();
926 m_endingPosition.clear();
927 m_leadingWhitespace.clear();
928 m_trailingWhitespace.clear();
mjsa0fe4042005-05-13 08:37:15 +0000929}
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000930
931String DeleteSelectionCommand::originalStringForAutocorrectionAtBeginningOfSelection()
932{
933 if (!m_selectionToDelete.isRange())
934 return String();
935
936 VisiblePosition startOfSelection = m_selectionToDelete.start();
937 if (!isStartOfWord(startOfSelection))
938 return String();
939
darin@apple.com5d3decc2020-08-01 15:50:36 +0000940 auto rangeOfFirstCharacter = makeSimpleRange(startOfSelection, startOfSelection.next());
941 if (!rangeOfFirstCharacter)
philn@webkit.org3d7a0f42011-05-12 14:36:29 +0000942 return String();
943
rniwa@webkit.org098af8c2021-04-25 05:59:59 +0000944 ScriptDisallowedScope scriptDisallowedScope;
darin@apple.com5d3decc2020-08-01 15:50:36 +0000945 for (auto* marker : document().markers().markersInRange(*rangeOfFirstCharacter, DocumentMarker::Autocorrected)) {
morrita@google.comc07dd582011-05-27 07:56:25 +0000946 int startOffset = marker->startOffset();
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000947 if (startOffset == startOfSelection.deepEquivalent().offsetInContainerNode())
morrita@google.comc07dd582011-05-27 07:56:25 +0000948 return marker->description();
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000949 }
950 return String();
951}
mjsa0fe4042005-05-13 08:37:15 +0000952
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000953// This method removes div elements with no attributes that have only one child or no children at all.
954void DeleteSelectionCommand::removeRedundantBlocks()
955{
cdumez@apple.come1840be2021-09-20 21:45:40 +0000956 RefPtr node { m_endingPosition.containerNode() };
Hironori.Fujii@sony.com9fa1f102021-04-20 20:21:16 +0000957 if (!node)
958 return;
cdumez@apple.come1840be2021-09-20 21:45:40 +0000959 RefPtr rootNode { node->rootEditableElement() };
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000960
commit-queue@webkit.org2ed8d902021-04-17 00:57:21 +0000961 while (node && node != rootNode) {
962 if (isRemovableBlock(node.get())) {
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000963 if (node == m_endingPosition.anchorNode())
cdumez@apple.com2cbdf512014-11-11 00:02:57 +0000964 updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node);
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000965
cdumez@apple.com2fecce72017-05-05 05:09:56 +0000966 CompositeEditCommand::removeNodePreservingChildren(*node);
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000967 node = m_endingPosition.anchorNode();
968 } else
enrica@apple.com48537332011-12-15 00:32:27 +0000969 node = node->parentNode();
enrica@apple.com7abf3f92011-12-01 01:16:13 +0000970 }
971}
972
mjsa0fe4042005-05-13 08:37:15 +0000973void DeleteSelectionCommand::doApply()
974{
975 // If selection has not been set to a custom selection when the command was created,
976 // use the current ending selection.
977 if (!m_hasSelectionToDelete)
978 m_selectionToDelete = endingSelection();
rniwa@webkit.orgd39a1e72010-08-25 19:19:26 +0000979
980 if (!m_selectionToDelete.isNonOrphanedRange())
mjsa0fe4042005-05-13 08:37:15 +0000981 return;
982
jpu@apple.comfd2014c2011-05-12 00:54:08 +0000983 String originalString = originalStringForAutocorrectionAtBeginningOfSelection();
984
adeleb2f2b662006-07-15 17:22:50 +0000985 // 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.
986 if (!m_replace) {
cdumez@apple.come1840be2021-09-20 21:45:40 +0000987 if (RefPtr textControl = enclosingTextFormControl(m_selectionToDelete.start()); textControl && textControl->focused())
wenson_hsieh@apple.com880f3502022-05-19 03:50:47 +0000988 document().editor().textWillBeDeletedInTextField(*textControl);
adeleb2f2b662006-07-15 17:22:50 +0000989 }
adele174364d2006-04-13 01:50:04 +0000990
mjsa0fe4042005-05-13 08:37:15 +0000991 // save this to later make the selection with
darin@apple.comc8d8b552020-09-03 21:38:50 +0000992 auto affinity = m_selectionToDelete.affinity();
993
justing11ec52e2006-06-07 00:09:37 +0000994 Position downstreamEnd = m_selectionToDelete.end().downstream();
commit-queue@webkit.orgf3628722010-11-28 02:54:43 +0000995 m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart(), CanCrossEditingBoundary)
996 && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditingBoundary)
commit-queue@webkit.org1d836ce2010-10-29 21:47:01 +0000997 && !lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd());
justing552eeaf2007-04-20 01:53:22 +0000998 if (m_needPlaceholder) {
999 // Don't need a placeholder when deleting a selection that starts just before a table
1000 // and ends inside it (we do need placeholders to hold open empty cells, but that's
1001 // handled elsewhere).
cdumez@apple.come1840be2021-09-20 21:45:40 +00001002 if (RefPtr table = isLastPositionBeforeTable(m_selectionToDelete.visibleStart())) {
cdumez@apple.comca12b502016-11-17 06:24:13 +00001003 if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(*table))
justing552eeaf2007-04-20 01:53:22 +00001004 m_needPlaceholder = false;
cdumez@apple.comca12b502016-11-17 06:24:13 +00001005 }
justing552eeaf2007-04-20 01:53:22 +00001006 }
1007
justing11ec52e2006-06-07 00:09:37 +00001008
mjsa0fe4042005-05-13 08:37:15 +00001009 // set up our state
wenson_hsieh@apple.com7cd854e2017-08-24 04:12:55 +00001010 if (!initializePositionData())
1011 return;
harrisondff01cd2005-07-18 23:21:19 +00001012
hyatt0c05e102006-04-14 08:15:00 +00001013 // Delete any text that may hinder our ability to fixup whitespace after the delete
harrisondff01cd2005-07-18 23:21:19 +00001014 deleteInsignificantTextDownstream(m_trailingWhitespace);
1015
1016 saveTypingStyleState();
mjsa0fe4042005-05-13 08:37:15 +00001017
harrisone4b84c52005-07-18 18:14:59 +00001018 // deleting just a BR is handled specially, at least because we do not
1019 // want to replace it with a placeholder BR!
1020 if (handleSpecialCaseBRDelete()) {
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +00001021 calculateTypingStyleAfterDelete();
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +00001022 setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
harrisone4b84c52005-07-18 18:14:59 +00001023 clearTransientState();
1024 rebalanceWhitespace();
1025 return;
1026 }
mjsa0fe4042005-05-13 08:37:15 +00001027
harrisone4b84c52005-07-18 18:14:59 +00001028 handleGeneralDelete();
mjsa0fe4042005-05-13 08:37:15 +00001029
mjsa0fe4042005-05-13 08:37:15 +00001030 fixupWhitespace();
mjsa0fe4042005-05-13 08:37:15 +00001031
justingf81641c2006-06-14 23:23:50 +00001032 mergeParagraphs();
1033
justing187d2922007-07-17 21:05:21 +00001034 removePreviouslySelectedEmptyTableRows();
1035
darin@apple.comd385be42016-05-14 20:09:50 +00001036 if (m_needPlaceholder) {
enrica@apple.comd14c2a82012-05-07 23:30:08 +00001037 if (m_sanitizeMarkup)
1038 removeRedundantBlocks();
shihchieh_lee@apple.com14ba3da2020-06-05 03:37:14 +00001039
1040 // FIXME (Bug 212723): m_endingPosition becomes disconnected in moveParagraph()
1041 // because it is ancestor of the deleted element and gets pruned in removeNodeAndPruneAncestors().
1042 // Either removeNodeAndPruneAncestors() should not remove ending position or we should find
1043 // a different ending position.
1044 if (!m_endingPosition.containerNode() || !m_endingPosition.containerNode()->isConnected())
1045 return;
darin@apple.comd385be42016-05-14 20:09:50 +00001046 insertNodeAt(HTMLBRElement::create(document()), m_endingPosition);
enrica@apple.com7abf3f92011-12-01 01:16:13 +00001047 }
mjsa0fe4042005-05-13 08:37:15 +00001048
enrica@apple.com2a79a802014-09-17 00:48:01 +00001049 bool shouldRebalaceWhiteSpace = true;
shihchieh_lee@apple.com501b37c2020-04-28 16:36:38 +00001050 if (!document().editor().behavior().shouldRebalanceWhiteSpacesInSecureField()) {
cdumez@apple.come1840be2021-09-20 21:45:40 +00001051 if (RefPtr endNode = m_endingPosition.deprecatedNode(); is<Text>(endNode)) {
rniwa@webkit.org098af8c2021-04-25 05:59:59 +00001052 auto& textNode = downcast<Text>(*endNode);
1053 ScriptDisallowedScope scriptDisallowedScope;
rniwa@webkit.org5c5bdba2016-07-21 19:41:47 +00001054 if (textNode.length() && textNode.renderer())
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +00001055 shouldRebalaceWhiteSpace = textNode.renderer()->style().textSecurity() == TextSecurity::None;
enrica@apple.com2a79a802014-09-17 00:48:01 +00001056 }
dbates@webkit.orgf435ee12014-01-10 17:06:52 +00001057 }
enrica@apple.com2a79a802014-09-17 00:48:01 +00001058 if (shouldRebalaceWhiteSpace)
dbates@webkit.orgf435ee12014-01-10 17:06:52 +00001059 rebalanceWhitespaceAt(m_endingPosition);
justing92cd9eb2006-12-06 21:01:30 +00001060
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +00001061 calculateTypingStyleAfterDelete();
jpu@apple.comfd2014c2011-05-12 00:54:08 +00001062
akling@apple.com6f18f6d2013-09-01 14:57:23 +00001063 if (!originalString.isEmpty())
shihchieh_lee@apple.com501b37c2020-04-28 16:36:38 +00001064 document().editor().deletedAutocorrectionAtPosition(m_endingPosition, originalString);
jpu@apple.comfd2014c2011-05-12 00:54:08 +00001065
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +00001066 setEndingSelection(VisibleSelection(m_endingPosition, affinity, endingSelection().isDirectional()));
mjsa0fe4042005-05-13 08:37:15 +00001067 clearTransientState();
mjsa0fe4042005-05-13 08:37:15 +00001068}
1069
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +00001070// Normally deletion doesn't preserve the typing style that was present before it. For example,
1071// type a character, Bold, then delete the character and start typing. The Bold typing style shouldn't
1072// stick around. Deletion should preserve a typing style that *it* sets, however.
mjsa0fe4042005-05-13 08:37:15 +00001073bool DeleteSelectionCommand::preservesTypingStyle() const
1074{
justin.garcia@apple.comd1eac9a2008-06-20 00:24:13 +00001075 return m_typingStyle;
mjsa0fe4042005-05-13 08:37:15 +00001076}
1077
darinb9481ed2006-03-20 02:57:59 +00001078} // namespace WebCore