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 "PlaceholderTransaction.h"
10 #include "CompositionTransaction.h"
11 #include "mozilla/EditorBase.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/ToString.h"
14 #include "mozilla/dom/Selection.h"
15 #include "nsGkAtoms.h"
16 #include "nsQueryObject.h"
22 PlaceholderTransaction::PlaceholderTransaction(
23 EditorBase
& aEditorBase
, nsStaticAtom
& aName
,
24 Maybe
<SelectionState
>&& aSelState
)
25 : mEditorBase(&aEditorBase
),
26 mCompositionTransaction(nullptr),
27 mStartSel(*std::move(aSelState
)),
33 NS_IMPL_CYCLE_COLLECTION_CLASS(PlaceholderTransaction
)
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PlaceholderTransaction
,
36 EditAggregateTransaction
)
37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase
);
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSel
);
39 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSel
);
40 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
42 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PlaceholderTransaction
,
43 EditAggregateTransaction
)
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase
);
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSel
);
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSel
);
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
49 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction
)
50 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction
)
52 NS_IMPL_ADDREF_INHERITED(PlaceholderTransaction
, EditAggregateTransaction
)
53 NS_IMPL_RELEASE_INHERITED(PlaceholderTransaction
, EditAggregateTransaction
)
55 NS_IMETHODIMP
PlaceholderTransaction::DoTransaction() {
57 GetLogModule(), LogLevel::Info
,
58 ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__
,
59 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
63 NS_IMETHODIMP
PlaceholderTransaction::UndoTransaction() {
64 MOZ_LOG(GetLogModule(), LogLevel::Info
,
65 ("%p PlaceholderTransaction::%s this={ mName=%s } "
66 "Start==============================",
68 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
70 if (NS_WARN_IF(!mEditorBase
)) {
71 return NS_ERROR_NOT_INITIALIZED
;
75 nsresult rv
= EditAggregateTransaction::UndoTransaction();
77 NS_WARNING("EditAggregateTransaction::UndoTransaction() failed");
81 // now restore selection
82 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
83 if (NS_WARN_IF(!selection
)) {
84 return NS_ERROR_FAILURE
;
86 rv
= mStartSel
.RestoreSelection(*selection
);
87 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
88 "SelectionState::RestoreSelection() failed");
90 MOZ_LOG(GetLogModule(), LogLevel::Info
,
91 ("%p PlaceholderTransaction::%s this={ mName=%s } "
92 "End==============================",
94 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
98 NS_IMETHODIMP
PlaceholderTransaction::RedoTransaction() {
99 MOZ_LOG(GetLogModule(), LogLevel::Info
,
100 ("%p PlaceholderTransaction::%s this={ mName=%s } "
101 "Start==============================",
103 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
105 if (NS_WARN_IF(!mEditorBase
)) {
106 return NS_ERROR_NOT_INITIALIZED
;
109 // Redo transactions.
110 nsresult rv
= EditAggregateTransaction::RedoTransaction();
112 NS_WARNING("EditAggregateTransaction::RedoTransaction() failed");
116 // now restore selection
117 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
118 if (NS_WARN_IF(!selection
)) {
119 return NS_ERROR_FAILURE
;
121 rv
= mEndSel
.RestoreSelection(*selection
);
122 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
123 "SelectionState::RestoreSelection() failed");
124 MOZ_LOG(GetLogModule(), LogLevel::Info
,
125 ("%p PlaceholderTransaction::%s this={ mName=%s } "
126 "End==============================",
128 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
132 NS_IMETHODIMP
PlaceholderTransaction::Merge(nsITransaction
* aOtherTransaction
,
134 if (NS_WARN_IF(!aDidMerge
) || NS_WARN_IF(!aOtherTransaction
)) {
135 return NS_ERROR_INVALID_ARG
;
138 // set out param default value
141 if (mForwardingTransaction
) {
142 MOZ_ASSERT_UNREACHABLE(
143 "tried to merge into a placeholder that was in "
145 return NS_ERROR_FAILURE
;
148 RefPtr
<EditTransactionBase
> otherTransactionBase
=
149 aOtherTransaction
->GetAsEditTransactionBase();
150 if (!otherTransactionBase
) {
151 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
152 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
153 "mName=%s } returned false due to non edit transaction",
154 this, __FUNCTION__
, aOtherTransaction
,
155 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
159 // We are absorbing all transactions if mAbsorb is lit.
161 if (CompositionTransaction
* otherCompositionTransaction
=
162 otherTransactionBase
->GetAsCompositionTransaction()) {
163 // special handling for CompositionTransaction's: they need to merge with
164 // any previous CompositionTransaction in this placeholder, if possible.
165 if (!mCompositionTransaction
) {
166 // this is the first IME txn in the placeholder
167 mCompositionTransaction
= otherCompositionTransaction
;
168 DebugOnly
<nsresult
> rvIgnored
=
169 AppendChild(otherCompositionTransaction
);
170 NS_WARNING_ASSERTION(
171 NS_SUCCEEDED(rvIgnored
),
172 "EditAggregateTransaction::AppendChild() failed, but ignored");
175 mCompositionTransaction
->Merge(otherCompositionTransaction
, &didMerge
);
177 // it wouldn't merge. Earlier IME txn is already committed and will
178 // not absorb further IME txns. So just stack this one after it
179 // and remember it as a candidate for further merges.
180 mCompositionTransaction
= otherCompositionTransaction
;
181 DebugOnly
<nsresult
> rvIgnored
=
182 AppendChild(otherCompositionTransaction
);
183 NS_WARNING_ASSERTION(
184 NS_SUCCEEDED(rvIgnored
),
185 "EditAggregateTransaction::AppendChild() failed, but ignored");
189 PlaceholderTransaction
* otherPlaceholderTransaction
=
190 otherTransactionBase
->GetAsPlaceholderTransaction();
191 if (!otherPlaceholderTransaction
) {
192 // See bug 171243: just drop incoming placeholders on the floor.
193 // Their children will be swallowed by this preexisting one.
194 DebugOnly
<nsresult
> rvIgnored
= AppendChild(otherTransactionBase
);
195 NS_WARNING_ASSERTION(
196 NS_SUCCEEDED(rvIgnored
),
197 "EditAggregateTransaction::AppendChild() failed, but ignored");
201 // RememberEndingSelection();
202 // efficiency hack: no need to remember selection here, as we haven't yet
203 // finished the initial batch and we know we will be told when the batch
204 // ends. we can remeber the selection then.
208 // merge typing or IME or deletion transactions if the selection matches
210 (mName
!= nsGkAtoms::TypingTxnName
&& mName
!= nsGkAtoms::IMETxnName
&&
211 mName
!= nsGkAtoms::DeleteTxnName
)) {
212 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
213 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
214 "mName=%s } returned false due to non mergable transaction",
215 this, __FUNCTION__
, aOtherTransaction
,
216 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
220 PlaceholderTransaction
* otherPlaceholderTransaction
=
221 otherTransactionBase
->GetAsPlaceholderTransaction();
222 if (!otherPlaceholderTransaction
) {
223 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
224 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
225 "mName=%s } returned false due to non placeholder transaction",
226 this, __FUNCTION__
, aOtherTransaction
,
227 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
231 RefPtr
<nsAtom
> otherTransactionName
;
232 DebugOnly
<nsresult
> rvIgnored
= otherPlaceholderTransaction
->GetName(
233 getter_AddRefs(otherTransactionName
));
234 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
235 "PlaceholderTransaction::GetName() failed, but ignored");
236 if (!otherTransactionName
|| otherTransactionName
== nsGkAtoms::_empty
||
237 otherTransactionName
!= mName
) {
238 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
239 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
240 "mName=%s } returned false due to non mergable placeholder "
242 this, __FUNCTION__
, aOtherTransaction
,
243 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
247 // check if start selection of next placeholder matches
248 // end selection of this placeholder
249 // XXX Theese checks seem wrong. The ending selection is initialized with
250 // actual Selection rather than expected Selection. Therefore, even when
251 // web apps modifies Selection, we don't merge mergable transactions.
253 // If the new transaction's starting Selection is not a caret, we shouldn't be
254 // merged with it because it's probably caused deleting the selection.
255 if (!otherPlaceholderTransaction
->mStartSel
.HasOnlyCollapsedRange()) {
256 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
257 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
258 "mName=%s } returned false due to not collapsed selection at "
259 "start of new transactions",
260 this, __FUNCTION__
, aOtherTransaction
,
261 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
265 // If our ending Selection is not a caret, we should not be merged with it
266 // because we probably changed format of a block or style of text.
267 if (!mEndSel
.HasOnlyCollapsedRange()) {
268 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
269 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
270 "mName=%s } returned false due to not collapsed selection at end "
271 "of previous transactions",
272 this, __FUNCTION__
, aOtherTransaction
,
273 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
277 // If the caret positions are now in different root nodes, e.g., the previous
278 // caret position was removed from the DOM tree, this merge should not be
280 const bool isPreviousCaretPointInSameRootOfNewCaretPoint
= [&]() {
281 nsINode
* previousRootInCurrentDOMTree
= mEndSel
.GetCommonRootNode();
282 return previousRootInCurrentDOMTree
&&
283 previousRootInCurrentDOMTree
==
284 otherPlaceholderTransaction
->mStartSel
.GetCommonRootNode();
286 if (!isPreviousCaretPointInSameRootOfNewCaretPoint
) {
287 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
288 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
289 "mName=%s } returned false due to the caret points are in "
290 "different root nodes",
291 this, __FUNCTION__
, aOtherTransaction
,
292 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
296 // If the caret points of end of us and start of new transaction are not same,
297 // we shouldn't merge them.
298 if (!otherPlaceholderTransaction
->mStartSel
.Equals(mEndSel
)) {
299 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
300 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
301 "mName=%s } returned false due to caret positions were different",
302 this, __FUNCTION__
, aOtherTransaction
,
303 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
307 mAbsorb
= true; // we need to start absorbing again
308 otherPlaceholderTransaction
->ForwardEndBatchTo(*this);
309 // AppendChild(editTransactionBase);
310 // see bug 171243: we don't need to merge placeholders
311 // into placeholders. We just reactivate merging in the
312 // pre-existing placeholder and drop the new one on the floor. The
313 // EndPlaceHolderBatch() call on the new placeholder will be
314 // forwarded to this older one.
315 rvIgnored
= RememberEndingSelection();
316 NS_WARNING_ASSERTION(
317 NS_SUCCEEDED(rvIgnored
),
318 "PlaceholderTransaction::RememberEndingSelection() failed, but "
321 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
322 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
323 "mName=%s } returned true",
324 this, __FUNCTION__
, aOtherTransaction
,
325 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
329 nsresult
PlaceholderTransaction::EndPlaceHolderBatch() {
332 if (mForwardingTransaction
) {
333 if (mForwardingTransaction
) {
334 DebugOnly
<nsresult
> rvIgnored
=
335 mForwardingTransaction
->EndPlaceHolderBatch();
336 NS_WARNING_ASSERTION(
337 NS_SUCCEEDED(rvIgnored
),
338 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored");
341 // remember our selection state.
342 nsresult rv
= RememberEndingSelection();
343 NS_WARNING_ASSERTION(
345 "PlaceholderTransaction::RememberEndingSelection() failed");
349 nsresult
PlaceholderTransaction::RememberEndingSelection() {
350 if (NS_WARN_IF(!mEditorBase
)) {
351 return NS_ERROR_NOT_INITIALIZED
;
354 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
355 if (NS_WARN_IF(!selection
)) {
356 return NS_ERROR_FAILURE
;
358 mEndSel
.SaveSelection(*selection
);
362 } // namespace mozilla