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 "DeleteNodeTransaction.h"
8 #include "HTMLEditUtils.h"
9 #include "mozilla/EditorBase.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/SelectionState.h" // RangeUpdater
12 #include "mozilla/TextEditor.h"
13 #include "mozilla/ToString.h"
16 #include "nsAString.h"
21 already_AddRefed
<DeleteNodeTransaction
> DeleteNodeTransaction::MaybeCreate(
22 EditorBase
& aEditorBase
, nsIContent
& aContentToDelete
) {
23 RefPtr
<DeleteNodeTransaction
> transaction
=
24 new DeleteNodeTransaction(aEditorBase
, aContentToDelete
);
25 if (NS_WARN_IF(!transaction
->CanDoIt())) {
28 return transaction
.forget();
31 DeleteNodeTransaction::DeleteNodeTransaction(EditorBase
& aEditorBase
,
32 nsIContent
& aContentToDelete
)
33 : mEditorBase(&aEditorBase
),
34 mContentToDelete(&aContentToDelete
),
35 mParentNode(aContentToDelete
.GetParentNode()) {
36 MOZ_DIAGNOSTIC_ASSERT_IF(
37 aEditorBase
.IsHTMLEditor(),
38 HTMLEditUtils::IsRemovableNode(aContentToDelete
) ||
39 // It's okay to delete text node if it's added by `HTMLEditor` since
40 // remaining it may be noisy for the users.
41 (aContentToDelete
.IsText() &&
42 aContentToDelete
.HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY
)));
44 !aEditorBase
.IsHTMLEditor() ||
45 HTMLEditUtils::IsRemovableNode(aContentToDelete
),
46 "Deleting non-editable text node, please write a test for this!!");
49 std::ostream
& operator<<(std::ostream
& aStream
,
50 const DeleteNodeTransaction
& aTransaction
) {
51 aStream
<< "{ mContentToDelete=" << aTransaction
.mContentToDelete
.get();
52 if (aTransaction
.mContentToDelete
) {
53 aStream
<< " (" << *aTransaction
.mContentToDelete
<< ")";
55 aStream
<< ", mParentNode=" << aTransaction
.mParentNode
.get();
56 if (aTransaction
.mParentNode
) {
57 aStream
<< " (" << *aTransaction
.mParentNode
<< ")";
59 aStream
<< ", mRefContent=" << aTransaction
.mRefContent
.get();
60 if (aTransaction
.mRefContent
) {
61 aStream
<< " (" << *aTransaction
.mRefContent
<< ")";
63 aStream
<< ", mEditorBase=" << aTransaction
.mEditorBase
.get() << " }";
67 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction
, EditTransactionBase
,
68 mEditorBase
, mContentToDelete
, mParentNode
,
71 NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction
, EditTransactionBase
)
72 NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction
, EditTransactionBase
)
73 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction
)
74 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase
)
76 bool DeleteNodeTransaction::CanDoIt() const {
77 if (NS_WARN_IF(!mContentToDelete
) || NS_WARN_IF(!mEditorBase
) ||
81 return mEditorBase
->IsTextEditor() ||
82 HTMLEditUtils::IsSimplyEditableNode(*mParentNode
);
85 NS_IMETHODIMP
DeleteNodeTransaction::DoTransaction() {
86 MOZ_LOG(GetLogModule(), LogLevel::Info
,
87 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
88 ToString(*this).c_str()));
90 if (NS_WARN_IF(!CanDoIt())) {
94 MOZ_ASSERT_IF(mEditorBase
->IsTextEditor(), !mContentToDelete
->IsText());
96 // Remember which child mContentToDelete was (by remembering which child was
97 // next). Note that mRefContent can be nullptr.
98 mRefContent
= mContentToDelete
->GetNextSibling();
100 // give range updater a chance. SelAdjDeleteNode() needs to be called
101 // *before* we do the action, unlike some of the other RangeItem update
103 mEditorBase
->RangeUpdaterRef().SelAdjDeleteNode(*mContentToDelete
);
105 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
106 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
108 parentNode
->RemoveChild(contentToDelete
, error
);
109 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
110 return error
.StealNSResult();
113 NS_IMETHODIMP
DeleteNodeTransaction::UndoTransaction() {
114 MOZ_LOG(GetLogModule(), LogLevel::Info
,
115 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
116 ToString(*this).c_str()));
118 if (NS_WARN_IF(!CanDoIt())) {
119 // This is a legal state, the transaction is a no-op.
123 OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
124 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
125 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
126 nsCOMPtr
<nsIContent
> refContent
= mRefContent
;
127 // XXX Perhaps, we should check `refContent` is a child of `parentNode`,
128 // and if it's not, we should stop undoing or something.
129 parentNode
->InsertBefore(contentToDelete
, refContent
, error
);
130 // InsertBefore() may call MightThrowJSException() even if there is no error.
131 // We don't need the flag here.
132 error
.WouldReportJSException();
133 if (error
.Failed()) {
134 NS_WARNING("nsINode::InsertBefore() failed");
135 return error
.StealNSResult();
140 NS_IMETHODIMP
DeleteNodeTransaction::RedoTransaction() {
141 MOZ_LOG(GetLogModule(), LogLevel::Info
,
142 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
143 ToString(*this).c_str()));
145 if (NS_WARN_IF(!CanDoIt())) {
146 // This is a legal state, the transaction is a no-op.
150 mEditorBase
->RangeUpdaterRef().SelAdjDeleteNode(*mContentToDelete
);
152 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
153 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
155 parentNode
->RemoveChild(contentToDelete
, error
);
156 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
157 return error
.StealNSResult();
160 } // namespace mozilla