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 "SplitNodeTransaction.h"
8 #include "EditorDOMPoint.h" // for EditorRawDOMPoint
9 #include "HTMLEditHelpers.h" // for SplitNodeResult
10 #include "HTMLEditor.h" // for HTMLEditor
11 #include "HTMLEditorInlines.h"
12 #include "HTMLEditUtils.h"
13 #include "SelectionState.h" // for AutoTrackDOMPoint and RangeUpdater
15 #include "mozilla/Logging.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/ToString.h"
18 #include "nsAString.h"
19 #include "nsDebug.h" // for NS_ASSERTION, etc.
20 #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc.
21 #include "nsIContent.h" // for nsIContent
27 template already_AddRefed
<SplitNodeTransaction
> SplitNodeTransaction::Create(
28 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aStartOfRightContent
);
29 template already_AddRefed
<SplitNodeTransaction
> SplitNodeTransaction::Create(
30 HTMLEditor
& aHTMLEditor
, const EditorRawDOMPoint
& aStartOfRightContent
);
33 template <typename PT
, typename CT
>
34 already_AddRefed
<SplitNodeTransaction
> SplitNodeTransaction::Create(
35 HTMLEditor
& aHTMLEditor
,
36 const EditorDOMPointBase
<PT
, CT
>& aStartOfRightContent
) {
37 RefPtr
<SplitNodeTransaction
> transaction
=
38 new SplitNodeTransaction(aHTMLEditor
, aStartOfRightContent
);
39 return transaction
.forget();
42 template <typename PT
, typename CT
>
43 SplitNodeTransaction::SplitNodeTransaction(
44 HTMLEditor
& aHTMLEditor
,
45 const EditorDOMPointBase
<PT
, CT
>& aStartOfRightContent
)
46 : mHTMLEditor(&aHTMLEditor
),
47 mSplitContent(aStartOfRightContent
.template GetContainerAs
<nsIContent
>()),
48 mSplitOffset(aStartOfRightContent
.Offset()) {
49 // printf("SplitNodeTransaction size: %zu\n", sizeof(SplitNodeTransaction));
50 static_assert(sizeof(SplitNodeTransaction
) <= 64,
51 "Transaction classes may be created a lot and may be alive "
52 "long so that keep the foot print smaller as far as possible");
53 MOZ_DIAGNOSTIC_ASSERT(aStartOfRightContent
.IsInContentNode());
54 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsSplittableNode(
55 *aStartOfRightContent
.template ContainerAs
<nsIContent
>()));
58 std::ostream
& operator<<(std::ostream
& aStream
,
59 const SplitNodeTransaction
& aTransaction
) {
60 aStream
<< "{ mParentNode=" << aTransaction
.mParentNode
.get();
61 if (aTransaction
.mParentNode
) {
62 aStream
<< " (" << *aTransaction
.mParentNode
<< ")";
64 aStream
<< ", mNewContent=" << aTransaction
.mNewContent
.get();
65 if (aTransaction
.mNewContent
) {
66 aStream
<< " (" << *aTransaction
.mNewContent
<< ")";
68 aStream
<< ", mSplitContent=" << aTransaction
.mSplitContent
.get();
69 if (aTransaction
.mSplitContent
) {
70 aStream
<< " (" << *aTransaction
.mSplitContent
<< ")";
72 aStream
<< ", mSplitOffset=" << aTransaction
.mSplitOffset
73 << ", mHTMLEditor=" << aTransaction
.mHTMLEditor
.get() << " }";
77 NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction
, EditTransactionBase
,
78 mHTMLEditor
, mParentNode
, mSplitContent
,
81 NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction
, EditTransactionBase
)
82 NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction
, EditTransactionBase
)
83 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction
)
84 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase
)
86 NS_IMETHODIMP
SplitNodeTransaction::DoTransaction() {
87 MOZ_LOG(GetLogModule(), LogLevel::Info
,
88 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__
,
89 ToString(*this).c_str()));
91 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor
) || NS_WARN_IF(!mSplitContent
))) {
92 return NS_ERROR_NOT_AVAILABLE
;
94 MOZ_ASSERT(mSplitOffset
<= mSplitContent
->Length());
97 IgnoredErrorResult error
;
98 // Don't use .downcast directly because AsContent has an assertion we want
99 nsCOMPtr
<nsINode
> newNode
= mSplitContent
->CloneNode(false, error
);
100 if (MOZ_UNLIKELY(error
.Failed())) {
101 NS_WARNING("nsINode::CloneNode() failed");
102 return error
.StealNSResult();
104 if (MOZ_UNLIKELY(NS_WARN_IF(!newNode
))) {
105 return NS_ERROR_UNEXPECTED
;
108 mNewContent
= newNode
->AsContent();
109 mParentNode
= mSplitContent
->GetParentNode();
111 NS_WARNING("The splitting content was an orphan node");
112 return NS_ERROR_NOT_AVAILABLE
;
115 const OwningNonNull
<HTMLEditor
> htmlEditor
= *mHTMLEditor
;
116 const OwningNonNull
<nsIContent
> splittingContent
= *mSplitContent
;
117 // MOZ_KnownLive(*mNewContent): it's grabbed by newNode
118 Result
<SplitNodeResult
, nsresult
> splitNodeResult
= DoTransactionInternal(
119 htmlEditor
, splittingContent
, MOZ_KnownLive(*mNewContent
), mSplitOffset
);
120 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
121 NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed");
122 return EditorBase::ToGenericNSResult(splitNodeResult
.unwrapErr());
124 // The user should handle selection rather here.
125 splitNodeResult
.inspect().IgnoreCaretPointSuggestion();
129 Result
<SplitNodeResult
, nsresult
> SplitNodeTransaction::DoTransactionInternal(
130 HTMLEditor
& aHTMLEditor
, nsIContent
& aSplittingContent
,
131 nsIContent
& aNewContent
, uint32_t aSplitOffset
) {
132 if (Element
* const splittingElement
= Element::FromNode(aSplittingContent
)) {
133 // MOZ_KnownLive(*splittingElement): aSplittingContent should be grabbed by
136 aHTMLEditor
.MarkElementDirty(MOZ_KnownLive(*splittingElement
));
137 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
138 return Err(NS_ERROR_EDITOR_DESTROYED
);
140 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
141 "EditorBase::MarkElementDirty() failed, but ignored");
144 Result
<SplitNodeResult
, nsresult
> splitNodeResult
= aHTMLEditor
.DoSplitNode(
145 EditorDOMPoint(&aSplittingContent
,
146 std::min(aSplitOffset
, aSplittingContent
.Length())),
148 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
149 NS_WARNING("HTMLEditor::DoSplitNode() failed");
150 return splitNodeResult
;
152 // When adding caret suggestion to SplitNodeResult, here didn't change
153 // selection so that just ignore it.
154 splitNodeResult
.inspect().IgnoreCaretPointSuggestion();
155 return splitNodeResult
;
158 NS_IMETHODIMP
SplitNodeTransaction::UndoTransaction() {
159 MOZ_LOG(GetLogModule(), LogLevel::Info
,
160 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__
,
161 ToString(*this).c_str()));
163 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor
) || NS_WARN_IF(!mNewContent
) ||
164 NS_WARN_IF(!mParentNode
) || NS_WARN_IF(!mSplitContent
) ||
165 NS_WARN_IF(mNewContent
->IsBeingRemoved()))) {
166 return NS_ERROR_NOT_AVAILABLE
;
169 // This assumes Do inserted the new node in front of the prior existing node
170 const OwningNonNull
<HTMLEditor
> htmlEditor
= *mHTMLEditor
;
171 const OwningNonNull
<nsIContent
> keepingContent
= *mSplitContent
;
172 const OwningNonNull
<nsIContent
> removingContent
= *mNewContent
;
173 EditorDOMPoint joinedPoint
;
174 // Unfortunately, we cannot track joining point if moving right node content
175 // into left node since it cannot track changes from web apps and HTMLEditor
176 // never removes the content of the left node. So it should be true that
177 // we don't need to track the point in this case.
178 nsresult rv
= htmlEditor
->DoJoinNodes(keepingContent
, removingContent
);
179 if (NS_SUCCEEDED(rv
)) {
180 // Adjust split offset for redo here
181 if (joinedPoint
.IsSet()) {
182 mSplitOffset
= joinedPoint
.Offset();
185 NS_WARNING("HTMLEditor::DoJoinNodes() failed");
190 /* Redo cannot simply resplit the right node, because subsequent transactions
191 * on the redo stack may depend on the left node existing in its previous
194 NS_IMETHODIMP
SplitNodeTransaction::RedoTransaction() {
195 MOZ_LOG(GetLogModule(), LogLevel::Info
,
196 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__
,
197 ToString(*this).c_str()));
199 if (MOZ_UNLIKELY(NS_WARN_IF(!mNewContent
) || NS_WARN_IF(!mParentNode
) ||
200 NS_WARN_IF(!mSplitContent
) || NS_WARN_IF(!mHTMLEditor
))) {
201 return NS_ERROR_NOT_AVAILABLE
;
204 const OwningNonNull
<HTMLEditor
> htmlEditor
= *mHTMLEditor
;
205 const OwningNonNull
<nsIContent
> newContent
= *mNewContent
;
206 const OwningNonNull
<nsIContent
> splittingContent
= *mSplitContent
;
207 Result
<SplitNodeResult
, nsresult
> splitNodeResult
= DoTransactionInternal(
208 htmlEditor
, splittingContent
, newContent
, mSplitOffset
);
209 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
210 NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed");
211 return EditorBase::ToGenericNSResult(splitNodeResult
.unwrapErr());
213 // When adding caret suggestion to SplitNodeResult, here didn't change
214 // selection so that just ignore it.
215 splitNodeResult
.inspect().IgnoreCaretPointSuggestion();
219 } // namespace mozilla