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 "JoinNodesTransaction.h"
8 #include "EditorDOMPoint.h" // for EditorDOMPoint, etc.
9 #include "HTMLEditHelpers.h" // for SplitNodeResult
10 #include "JoinSplitNodeDirection.h" // JoinNodesDirection
11 #include "HTMLEditor.h" // for HTMLEditor
12 #include "HTMLEditorInlines.h"
13 #include "HTMLEditUtils.h"
15 #include "mozilla/Logging.h"
16 #include "mozilla/ToString.h"
17 #include "mozilla/dom/Text.h"
19 #include "nsAString.h"
20 #include "nsDebug.h" // for NS_ASSERTION, etc.
21 #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
22 #include "nsIContent.h" // for nsIContent
23 #include "nsISupportsImpl.h" // for QueryInterface, etc.
30 already_AddRefed
<JoinNodesTransaction
> JoinNodesTransaction::MaybeCreate(
31 HTMLEditor
& aHTMLEditor
, nsIContent
& aLeftContent
,
32 nsIContent
& aRightContent
) {
33 RefPtr
<JoinNodesTransaction
> transaction
=
34 new JoinNodesTransaction(aHTMLEditor
, aLeftContent
, aRightContent
);
35 if (NS_WARN_IF(!transaction
->CanDoIt())) {
38 return transaction
.forget();
41 JoinNodesTransaction::JoinNodesTransaction(HTMLEditor
& aHTMLEditor
,
42 nsIContent
& aLeftContent
,
43 nsIContent
& aRightContent
)
44 : mHTMLEditor(&aHTMLEditor
),
45 mRemovedContent(aHTMLEditor
.GetJoinNodesDirection() ==
46 JoinNodesDirection::LeftNodeIntoRightNode
49 mKeepingContent(aHTMLEditor
.GetJoinNodesDirection() ==
50 JoinNodesDirection::LeftNodeIntoRightNode
53 // printf("JoinNodesTransaction size: %zu\n", sizeof(JoinNodesTransaction));
54 static_assert(sizeof(JoinNodesTransaction
) <= 64,
55 "Transaction classes may be created a lot and may be alive "
56 "long so that keep the foot print smaller as far as possible");
59 std::ostream
& operator<<(std::ostream
& aStream
,
60 const JoinNodesTransaction
& aTransaction
) {
61 aStream
<< "{ mParentNode=" << aTransaction
.mParentNode
.get();
62 if (aTransaction
.mParentNode
) {
63 aStream
<< " (" << *aTransaction
.mParentNode
<< ")";
65 aStream
<< ", mRemovedContent=" << aTransaction
.mRemovedContent
.get();
66 if (aTransaction
.mRemovedContent
) {
67 aStream
<< " (" << *aTransaction
.mRemovedContent
<< ")";
69 aStream
<< ", mKeepingContent=" << aTransaction
.mKeepingContent
.get();
70 if (aTransaction
.mKeepingContent
) {
71 aStream
<< " (" << *aTransaction
.mKeepingContent
<< ")";
73 aStream
<< ", mJoinedOffset=" << aTransaction
.mJoinedOffset
74 << ", mHTMLEditor=" << aTransaction
.mHTMLEditor
.get()
75 << ", GetJoinNodesDirection()="
76 << aTransaction
.GetJoinNodesDirection() << " }";
80 NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodesTransaction
, EditTransactionBase
,
81 mHTMLEditor
, mParentNode
, mRemovedContent
,
84 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodesTransaction
)
85 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase
)
87 SplitNodeDirection
JoinNodesTransaction::GetSplitNodeDirection() const {
88 return MOZ_LIKELY(mHTMLEditor
) ? mHTMLEditor
->GetSplitNodeDirection()
89 : SplitNodeDirection::LeftNodeIsNewOne
;
92 JoinNodesDirection
JoinNodesTransaction::GetJoinNodesDirection() const {
93 return MOZ_LIKELY(mHTMLEditor
) ? mHTMLEditor
->GetJoinNodesDirection()
94 : JoinNodesDirection::LeftNodeIntoRightNode
;
97 bool JoinNodesTransaction::CanDoIt() const {
98 if (NS_WARN_IF(!mKeepingContent
) || NS_WARN_IF(!mRemovedContent
) ||
99 NS_WARN_IF(!mHTMLEditor
) ||
100 NS_WARN_IF(mRemovedContent
->IsBeingRemoved()) ||
101 !mKeepingContent
->IsInComposedDoc()) {
104 return HTMLEditUtils::IsRemovableFromParentNode(*mRemovedContent
);
107 // After DoTransaction() and RedoTransaction(), the left node is removed from
108 // the content tree and right node remains.
109 NS_IMETHODIMP
JoinNodesTransaction::DoTransaction() {
110 MOZ_LOG(GetLogModule(), LogLevel::Info
,
111 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__
,
112 ToString(*this).c_str()));
114 return DoTransactionInternal(RedoingTransaction::No
);
117 nsresult
JoinNodesTransaction::DoTransactionInternal(
118 RedoingTransaction aRedoingTransaction
) {
119 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor
) || NS_WARN_IF(!mKeepingContent
) ||
120 NS_WARN_IF(!mRemovedContent
) ||
121 NS_WARN_IF(mRemovedContent
->IsBeingRemoved()))) {
122 return NS_ERROR_NOT_AVAILABLE
;
125 nsINode
* removingContentParentNode
= mRemovedContent
->GetParentNode();
126 if (MOZ_UNLIKELY(NS_WARN_IF(!removingContentParentNode
))) {
127 return NS_ERROR_NOT_AVAILABLE
;
130 // Verify that the joining content nodes have the same parent
131 if (MOZ_UNLIKELY(removingContentParentNode
!=
132 mKeepingContent
->GetParentNode())) {
133 NS_ASSERTION(false, "Nodes do not have same parent");
134 return NS_ERROR_NOT_AVAILABLE
;
137 // Set this instance's mParentNode. Other methods will see a non-null
138 // mParentNode and know all is well
139 mParentNode
= removingContentParentNode
;
140 // For now, setting mJoinedOffset to removed content length so that
141 // CreateJoinedPoint returns a point in mKeepingContent whose offset is
142 // the result if all content in mRemovedContent are moved to start or end of
143 // mKeepingContent without any intervation. The offset will be adjusted
146 GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode
147 ? mRemovedContent
->Length()
148 : mKeepingContent
->Length();
150 const OwningNonNull
<HTMLEditor
> htmlEditor
= *mHTMLEditor
;
151 const OwningNonNull
<nsIContent
> removingContent
= *mRemovedContent
;
152 const OwningNonNull
<nsIContent
> keepingContent
= *mKeepingContent
;
154 // Let's try to get actual joined point with the tacker.
155 EditorDOMPoint joinNodesPoint
=
156 GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode
157 ? EditorDOMPoint(keepingContent
, 0u)
158 : EditorDOMPoint::AtEndOf(keepingContent
);
160 AutoTrackDOMPoint
trackJoinNodePoint(htmlEditor
->RangeUpdaterRef(),
162 rv
= htmlEditor
->DoJoinNodes(keepingContent
, removingContent
,
163 GetJoinNodesDirection());
164 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "HTMLEditor::DoJoinNodes() failed");
166 // Adjust join node offset to the actual offset where the original first
167 // content of the right node is.
168 mJoinedOffset
= joinNodesPoint
.Offset();
170 if (aRedoingTransaction
== RedoingTransaction::No
) {
171 htmlEditor
->DidJoinNodesTransaction(*this, rv
);
177 // XXX: What if instead of split, we just deleted the unneeded children of
178 // mRight and re-inserted mLeft?
179 NS_IMETHODIMP
JoinNodesTransaction::UndoTransaction() {
180 MOZ_LOG(GetLogModule(), LogLevel::Info
,
181 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__
,
182 ToString(*this).c_str()));
184 if (NS_WARN_IF(!mParentNode
) || NS_WARN_IF(!mKeepingContent
) ||
185 NS_WARN_IF(!mRemovedContent
) || NS_WARN_IF(!mHTMLEditor
)) {
186 return NS_ERROR_NOT_AVAILABLE
;
189 const OwningNonNull
<HTMLEditor
> htmlEditor
= *mHTMLEditor
;
190 const OwningNonNull
<nsIContent
> removedContent
= *mRemovedContent
;
192 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
193 htmlEditor
->DoSplitNode(CreateJoinedPoint
<EditorDOMPoint
>(),
194 removedContent
, GetSplitNodeDirection());
195 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
196 NS_WARNING("HTMLEditor::DoSplitNode() failed");
197 return splitNodeResult
.unwrapErr();
199 // When adding caret suggestion to SplitNodeResult, here didn't change
200 // selection so that just ignore it.
201 splitNodeResult
.inspect().IgnoreCaretPointSuggestion();
205 NS_IMETHODIMP
JoinNodesTransaction::RedoTransaction() {
206 MOZ_LOG(GetLogModule(), LogLevel::Info
,
207 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__
,
208 ToString(*this).c_str()));
209 return DoTransactionInternal(RedoingTransaction::Yes
);
212 } // namespace mozilla