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"
27 #include "nsGkAtoms.h"
28 #include "nsIContent.h"
30 #include "nsAString.h"
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
,
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==============================",
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());
77 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteBetween() failed");
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
);
85 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteContent() failed");
88 // delete the intervening nodes
89 rv
= CreateTxnsToDeleteNodesBetween(rangeToDelete
);
92 "DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween() failed");
95 // delete the relevant content in the end node
96 rv
= CreateTxnsToDeleteContent(endRef
.AsRaw(), nsIEditor::ePrevious
);
98 NS_WARNING("DeleteRangeTransaction::CreateTxnsToDeleteContent() failed");
103 // if we've successfully built this aggregate transaction, then do it.
104 nsresult rv
= EditAggregateTransaction::DoTransaction();
106 NS_WARNING("EditAggregateTransaction::DoTransaction() failed");
110 MOZ_LOG(GetLogModule(), LogLevel::Info
,
111 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
112 "End==============================",
114 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
116 if (!mEditorBase
->AllowsTransactionsToChangeSelection()) {
120 OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
121 rv
= editorBase
->CollapseSelectionTo(EditorRawDOMPoint(startRef
));
122 NS_WARNING_ASSERTION(
124 "EditorBase::CollapseSelectionToEndOfLastLeafNode() failed");
128 NS_IMETHODIMP
DeleteRangeTransaction::UndoTransaction() {
129 MOZ_LOG(GetLogModule(), LogLevel::Info
,
130 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
131 "Start==============================",
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==============================",
143 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
147 NS_IMETHODIMP
DeleteRangeTransaction::RedoTransaction() {
148 MOZ_LOG(GetLogModule(), LogLevel::Info
,
149 ("%p DeleteRangeTransaction::%s this={ mName=%s } "
150 "Start==============================",
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==============================",
162 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
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() &&
182 !EditorUtils::IsEditableContent(*textNode
, EditorType::HTML
))) {
183 // Just ignore to append the transaction for non-editable node.
186 // if the node is a chardata node, then delete chardata content
187 uint32_t textLengthToDelete
;
188 if (aStart
== aEnd
) {
189 textLengthToDelete
= 1;
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
),
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");
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");
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());
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
;
262 numToDelete
= *aPoint
.Offset(RawRangeBoundary::OffsetFilter::kValidOffsets
);
269 RefPtr
<DeleteTextTransaction
> deleteTextTransaction
=
270 DeleteTextTransaction::MaybeCreate(*mEditorBase
, *textNode
, startOffset
,
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");
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
);
295 NS_WARNING("ContentSubtreeIterator::Init() failed");
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()))) {
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");
320 } // namespace mozilla