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 "EditorBase.h"
9 #include "EditorDOMPoint.h"
10 #include "HTMLEditUtils.h"
11 #include "SelectionState.h" // RangeUpdater
12 #include "TextEditor.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/ToString.h"
19 #include "nsAString.h"
24 already_AddRefed
<DeleteNodeTransaction
> DeleteNodeTransaction::MaybeCreate(
25 EditorBase
& aEditorBase
, nsIContent
& aContentToDelete
) {
26 RefPtr
<DeleteNodeTransaction
> transaction
=
27 new DeleteNodeTransaction(aEditorBase
, aContentToDelete
);
28 if (NS_WARN_IF(!transaction
->CanDoIt())) {
31 return transaction
.forget();
34 DeleteNodeTransaction::DeleteNodeTransaction(EditorBase
& aEditorBase
,
35 nsIContent
& aContentToDelete
)
36 : DeleteContentTransactionBase(aEditorBase
),
37 mContentToDelete(&aContentToDelete
),
38 mParentNode(aContentToDelete
.GetParentNode()) {
39 MOZ_DIAGNOSTIC_ASSERT_IF(
40 aEditorBase
.IsHTMLEditor(),
41 HTMLEditUtils::IsRemovableNode(aContentToDelete
) ||
42 // It's okay to delete text node if it's added by `HTMLEditor` since
43 // remaining it may be noisy for the users.
44 (aContentToDelete
.IsText() &&
45 aContentToDelete
.HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY
)));
47 !aEditorBase
.IsHTMLEditor() ||
48 HTMLEditUtils::IsRemovableNode(aContentToDelete
),
49 "Deleting non-editable text node, please write a test for this!!");
52 std::ostream
& operator<<(std::ostream
& aStream
,
53 const DeleteNodeTransaction
& aTransaction
) {
54 aStream
<< "{ mContentToDelete=" << aTransaction
.mContentToDelete
.get();
55 if (aTransaction
.mContentToDelete
) {
56 aStream
<< " (" << *aTransaction
.mContentToDelete
<< ")";
58 aStream
<< ", mParentNode=" << aTransaction
.mParentNode
.get();
59 if (aTransaction
.mParentNode
) {
60 aStream
<< " (" << *aTransaction
.mParentNode
<< ")";
62 aStream
<< ", mRefContent=" << aTransaction
.mRefContent
.get();
63 if (aTransaction
.mRefContent
) {
64 aStream
<< " (" << *aTransaction
.mRefContent
<< ")";
66 aStream
<< ", mEditorBase=" << aTransaction
.mEditorBase
.get() << " }";
70 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction
,
71 DeleteContentTransactionBase
,
72 mContentToDelete
, mParentNode
, mRefContent
)
74 NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction
, DeleteContentTransactionBase
)
75 NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction
, DeleteContentTransactionBase
)
76 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction
)
77 NS_INTERFACE_MAP_END_INHERITING(DeleteContentTransactionBase
)
79 bool DeleteNodeTransaction::CanDoIt() const {
80 if (NS_WARN_IF(!mContentToDelete
) || NS_WARN_IF(!mEditorBase
) ||
84 return mEditorBase
->IsTextEditor() ||
85 HTMLEditUtils::IsSimplyEditableNode(*mParentNode
);
88 NS_IMETHODIMP
DeleteNodeTransaction::DoTransaction() {
89 MOZ_LOG(GetLogModule(), LogLevel::Info
,
90 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
91 ToString(*this).c_str()));
93 if (NS_WARN_IF(!CanDoIt())) {
97 MOZ_ASSERT_IF(mEditorBase
->IsTextEditor(), !mContentToDelete
->IsText());
99 // Remember which child mContentToDelete was (by remembering which child was
100 // next). Note that mRefContent can be nullptr.
101 mRefContent
= mContentToDelete
->GetNextSibling();
103 // give range updater a chance. SelAdjDeleteNode() needs to be called
104 // *before* we do the action, unlike some of the other RangeItem update
106 mEditorBase
->RangeUpdaterRef().SelAdjDeleteNode(*mContentToDelete
);
108 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
109 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
111 parentNode
->RemoveChild(contentToDelete
, error
);
112 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
113 return error
.StealNSResult();
116 EditorDOMPoint
DeleteNodeTransaction::SuggestPointToPutCaret() const {
117 return EditorDOMPoint();
120 NS_IMETHODIMP
DeleteNodeTransaction::UndoTransaction() {
121 MOZ_LOG(GetLogModule(), LogLevel::Info
,
122 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
123 ToString(*this).c_str()));
125 if (NS_WARN_IF(!CanDoIt())) {
126 // This is a legal state, the transaction is a no-op.
130 OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
131 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
132 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
133 nsCOMPtr
<nsIContent
> refContent
= mRefContent
;
134 // XXX Perhaps, we should check `refContent` is a child of `parentNode`,
135 // and if it's not, we should stop undoing or something.
136 parentNode
->InsertBefore(contentToDelete
, refContent
, error
);
137 // InsertBefore() may call MightThrowJSException() even if there is no error.
138 // We don't need the flag here.
139 error
.WouldReportJSException();
140 if (error
.Failed()) {
141 NS_WARNING("nsINode::InsertBefore() failed");
142 return error
.StealNSResult();
147 NS_IMETHODIMP
DeleteNodeTransaction::RedoTransaction() {
148 MOZ_LOG(GetLogModule(), LogLevel::Info
,
149 ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__
,
150 ToString(*this).c_str()));
152 if (NS_WARN_IF(!CanDoIt())) {
153 // This is a legal state, the transaction is a no-op.
157 mEditorBase
->RangeUpdaterRef().SelAdjDeleteNode(*mContentToDelete
);
159 OwningNonNull
<nsINode
> parentNode
= *mParentNode
;
160 OwningNonNull
<nsIContent
> contentToDelete
= *mContentToDelete
;
162 parentNode
->RemoveChild(contentToDelete
, error
);
163 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
164 return error
.StealNSResult();
167 } // namespace mozilla