Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / DeleteRangeTransaction.cpp
blob73445419d77be3463455b891e9941843d0e8da34
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DeleteRangeTransaction.h"
8 #include "DeleteNodeTransaction.h"
9 #include "DeleteTextTransaction.h"
10 #include "EditorBase.h"
11 #include "EditorDOMPoint.h"
12 #include "EditorUtils.h"
13 #include "HTMLEditUtils.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/ContentIterator.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/mozalloc.h"
19 #include "mozilla/RangeBoundary.h"
20 #include "mozilla/ToString.h"
21 #include "mozilla/dom/Selection.h"
23 #include "nsAtom.h"
24 #include "nsCOMPtr.h"
25 #include "nsDebug.h"
26 #include "nsError.h"
27 #include "nsGkAtoms.h"
28 #include "nsIContent.h"
29 #include "nsINode.h"
30 #include "nsAString.h"
32 namespace mozilla {
34 using namespace dom;
36 using EditorType = EditorUtils::EditorType;
38 DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
39 const nsRange& aRangeToDelete)
40 : mEditorBase(&aEditorBase), mRangeToDelete(aRangeToDelete.CloneRange()) {}
42 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
43 EditAggregateTransaction, mEditorBase,
44 mRangeToDelete)
46 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
47 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
49 NS_IMETHODIMP DeleteRangeTransaction::DoTransaction() {
50 MOZ_LOG(GetLogModule(), LogLevel::Info,
51 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
52 "Start==============================",
53 this, __FUNCTION__,
54 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
56 if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mRangeToDelete)) {
57 return NS_ERROR_NOT_AVAILABLE;
60 // Swap mRangeToDelete out into a stack variable, so we make sure to null it
61 // out on return from this function. Once this function returns, we no longer
62 // need mRangeToDelete, and keeping it alive in the long term slows down all
63 // DOM mutations because it's observing them.
64 RefPtr<nsRange> rangeToDelete;
65 rangeToDelete.swap(mRangeToDelete);
67 // build the child transactions
68 const RangeBoundary& startRef = rangeToDelete->StartRef();
69 const RangeBoundary& endRef = rangeToDelete->EndRef();
70 MOZ_ASSERT(startRef.IsSetAndValid());
71 MOZ_ASSERT(endRef.IsSetAndValid());
73 if (startRef.Container() == endRef.Container()) {
74 // the selection begins and ends in the same node
75 nsresult rv = CreateTxnsToDeleteBetween(startRef.AsRaw(), endRef.AsRaw());
76 if (NS_FAILED(rv)) {
77 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteBetween() failed");
78 return rv;
80 } else {
81 // the selection ends in a different node from where it started. delete
82 // the relevant content in the start node
83 nsresult rv = CreateTxnsToDeleteContent(startRef.AsRaw(), nsIEditor::eNext);
84 if (NS_FAILED(rv)) {
85 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteContent() failed");
86 return rv;
88 // delete the intervening nodes
89 rv = CreateTxnsToDeleteNodesBetween(rangeToDelete);
90 if (NS_FAILED(rv)) {
91 NS_WARNING(
92 "DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween() failed");
93 return rv;
95 // delete the relevant content in the end node
96 rv = CreateTxnsToDeleteContent(endRef.AsRaw(), nsIEditor::ePrevious);
97 if (NS_FAILED(rv)) {
98 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteContent() failed");
99 return rv;
103 // if we've successfully built this aggregate transaction, then do it.
104 nsresult rv = EditAggregateTransaction::DoTransaction();
105 if (NS_FAILED(rv)) {
106 NS_WARNING("EditAggregateTransaction::DoTransaction() failed");
107 return rv;
110 MOZ_LOG(GetLogModule(), LogLevel::Info,
111 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
112 "End==============================",
113 this, __FUNCTION__,
114 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
116 if (!mEditorBase->AllowsTransactionsToChangeSelection()) {
117 return NS_OK;
120 OwningNonNull<EditorBase> editorBase = *mEditorBase;
121 rv = editorBase->CollapseSelectionTo(EditorRawDOMPoint(startRef));
122 NS_WARNING_ASSERTION(
123 NS_SUCCEEDED(rv),
124 "EditorBase::CollapseSelectionToEndOfLastLeafNode() failed");
125 return rv;
128 NS_IMETHODIMP DeleteRangeTransaction::UndoTransaction() {
129 MOZ_LOG(GetLogModule(), LogLevel::Info,
130 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
131 "Start==============================",
132 this, __FUNCTION__,
133 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
135 nsresult rv = EditAggregateTransaction::UndoTransaction();
136 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
137 "EditAggregateTransaction::UndoTransaction() failed");
139 MOZ_LOG(GetLogModule(), LogLevel::Info,
140 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
141 "End==============================",
142 this, __FUNCTION__,
143 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
144 return rv;
147 NS_IMETHODIMP DeleteRangeTransaction::RedoTransaction() {
148 MOZ_LOG(GetLogModule(), LogLevel::Info,
149 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
150 "Start==============================",
151 this, __FUNCTION__,
152 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
154 nsresult rv = EditAggregateTransaction::RedoTransaction();
155 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
156 "EditAggregateTransaction::RedoTransaction() failed");
158 MOZ_LOG(GetLogModule(), LogLevel::Info,
159 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
160 "End==============================",
161 this, __FUNCTION__,
162 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
163 return rv;
166 nsresult DeleteRangeTransaction::CreateTxnsToDeleteBetween(
167 const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) {
168 if (NS_WARN_IF(!aStart.IsSetAndValid()) ||
169 NS_WARN_IF(!aEnd.IsSetAndValid()) ||
170 NS_WARN_IF(aStart.Container() != aEnd.Container())) {
171 return NS_ERROR_INVALID_ARG;
174 if (NS_WARN_IF(!mEditorBase)) {
175 return NS_ERROR_NOT_AVAILABLE;
178 // see what kind of node we have
179 if (Text* textNode = Text::FromNode(aStart.Container())) {
180 if (mEditorBase->IsHTMLEditor() &&
181 NS_WARN_IF(
182 !EditorUtils::IsEditableContent(*textNode, EditorType::HTML))) {
183 // Just ignore to append the transaction for non-editable node.
184 return NS_OK;
186 // if the node is a chardata node, then delete chardata content
187 uint32_t textLengthToDelete;
188 if (aStart == aEnd) {
189 textLengthToDelete = 1;
190 } else {
191 textLengthToDelete =
192 *aEnd.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets) -
193 *aStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets);
194 MOZ_DIAGNOSTIC_ASSERT(textLengthToDelete > 0);
197 RefPtr<DeleteTextTransaction> deleteTextTransaction =
198 DeleteTextTransaction::MaybeCreate(
199 *mEditorBase, *textNode,
200 *aStart.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets),
201 textLengthToDelete);
202 // If the text node isn't editable, it should be never undone/redone.
203 // So, the transaction shouldn't be recorded.
204 if (!deleteTextTransaction) {
205 NS_WARNING("DeleteTextTransaction::MaybeCreate() failed");
206 return NS_ERROR_FAILURE;
208 DebugOnly<nsresult> rvIgnored = AppendChild(deleteTextTransaction);
209 NS_WARNING_ASSERTION(
210 NS_SUCCEEDED(rvIgnored),
211 "DeleteRangeTransaction::AppendChild() failed, but ignored");
212 return NS_OK;
215 MOZ_ASSERT(mEditorBase->IsHTMLEditor());
217 // Even if we detect invalid range, we should ignore it for removing
218 // specified range's nodes as far as possible.
219 // XXX This is super expensive. Probably, we should make
220 // DeleteNodeTransaction() can treat multiple siblings.
221 for (nsIContent* child = aStart.GetChildAtOffset();
222 child && child != aEnd.GetChildAtOffset();
223 child = child->GetNextSibling()) {
224 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*child))) {
225 continue; // Should we abort?
227 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
228 DeleteNodeTransaction::MaybeCreate(*mEditorBase, *child);
229 if (deleteNodeTransaction) {
230 DebugOnly<nsresult> rvIgnored = AppendChild(deleteNodeTransaction);
231 NS_WARNING_ASSERTION(
232 NS_SUCCEEDED(rvIgnored),
233 "DeleteRangeTransaction::AppendChild() failed, but ignored");
237 return NS_OK;
240 nsresult DeleteRangeTransaction::CreateTxnsToDeleteContent(
241 const RawRangeBoundary& aPoint, nsIEditor::EDirection aAction) {
242 if (NS_WARN_IF(!aPoint.IsSetAndValid())) {
243 return NS_ERROR_INVALID_ARG;
246 if (NS_WARN_IF(!mEditorBase)) {
247 return NS_ERROR_NOT_AVAILABLE;
250 Text* textNode = Text::FromNode(aPoint.Container());
251 if (!textNode) {
252 return NS_OK;
255 // If the node is a chardata node, then delete chardata content
256 uint32_t startOffset, numToDelete;
257 if (nsIEditor::eNext == aAction) {
258 startOffset = *aPoint.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets);
259 numToDelete = aPoint.Container()->Length() - startOffset;
260 } else {
261 startOffset = 0;
262 numToDelete = *aPoint.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets);
265 if (!numToDelete) {
266 return NS_OK;
269 RefPtr<DeleteTextTransaction> deleteTextTransaction =
270 DeleteTextTransaction::MaybeCreate(*mEditorBase, *textNode, startOffset,
271 numToDelete);
272 NS_WARNING_ASSERTION(deleteTextTransaction,
273 "DeleteTextTransaction::MaybeCreate() failed");
274 // If the text node isn't editable, it should be never undone/redone.
275 // So, the transaction shouldn't be recorded.
276 if (!deleteTextTransaction) {
277 return NS_ERROR_FAILURE;
279 DebugOnly<nsresult> rvIgnored = AppendChild(deleteTextTransaction);
280 NS_WARNING_ASSERTION(
281 NS_SUCCEEDED(rvIgnored),
282 "DeleteRangeTransaction::AppendChild() failed, but ignored");
283 return NS_OK;
286 nsresult DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween(
287 nsRange* aRangeToDelete) {
288 if (NS_WARN_IF(!mEditorBase)) {
289 return NS_ERROR_NOT_AVAILABLE;
292 ContentSubtreeIterator subtreeIter;
293 nsresult rv = subtreeIter.Init(aRangeToDelete);
294 if (NS_FAILED(rv)) {
295 NS_WARNING("ContentSubtreeIterator::Init() failed");
296 return rv;
299 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
300 nsINode* node = subtreeIter.GetCurrentNode();
301 if (NS_WARN_IF(!node) || NS_WARN_IF(!node->IsContent())) {
302 return NS_ERROR_FAILURE;
305 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*node->AsContent()))) {
306 continue;
308 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
309 DeleteNodeTransaction::MaybeCreate(*mEditorBase, *node->AsContent());
310 if (deleteNodeTransaction) {
311 DebugOnly<nsresult> rvIgnored = AppendChild(deleteNodeTransaction);
312 NS_WARNING_ASSERTION(
313 NS_SUCCEEDED(rvIgnored),
314 "DeleteRangeTransaction::AppendChild() failed, but ignored");
317 return NS_OK;
320 } // namespace mozilla