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 void PlaceholderTransaction::AppendChild(EditTransactionBase
& aTransaction
) {
56 mChildren
.AppendElement(aTransaction
);
59 NS_IMETHODIMP
PlaceholderTransaction::DoTransaction() {
61 GetLogModule(), LogLevel::Info
,
62 ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__
,
63 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
67 NS_IMETHODIMP
PlaceholderTransaction::UndoTransaction() {
68 MOZ_LOG(GetLogModule(), LogLevel::Info
,
69 ("%p PlaceholderTransaction::%s this={ mName=%s } "
70 "Start==============================",
72 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
74 if (NS_WARN_IF(!mEditorBase
)) {
75 return NS_ERROR_NOT_INITIALIZED
;
79 nsresult rv
= EditAggregateTransaction::UndoTransaction();
81 NS_WARNING("EditAggregateTransaction::UndoTransaction() failed");
85 // now restore selection
86 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
87 if (NS_WARN_IF(!selection
)) {
88 return NS_ERROR_FAILURE
;
90 rv
= mStartSel
.RestoreSelection(*selection
);
91 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
92 "SelectionState::RestoreSelection() failed");
94 MOZ_LOG(GetLogModule(), LogLevel::Info
,
95 ("%p PlaceholderTransaction::%s this={ mName=%s } "
96 "End==============================",
98 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
102 NS_IMETHODIMP
PlaceholderTransaction::RedoTransaction() {
103 MOZ_LOG(GetLogModule(), LogLevel::Info
,
104 ("%p PlaceholderTransaction::%s this={ mName=%s } "
105 "Start==============================",
107 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
109 if (NS_WARN_IF(!mEditorBase
)) {
110 return NS_ERROR_NOT_INITIALIZED
;
113 // Redo transactions.
114 nsresult rv
= EditAggregateTransaction::RedoTransaction();
116 NS_WARNING("EditAggregateTransaction::RedoTransaction() failed");
120 // now restore selection
121 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
122 if (NS_WARN_IF(!selection
)) {
123 return NS_ERROR_FAILURE
;
125 rv
= mEndSel
.RestoreSelection(*selection
);
126 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
127 "SelectionState::RestoreSelection() failed");
128 MOZ_LOG(GetLogModule(), LogLevel::Info
,
129 ("%p PlaceholderTransaction::%s this={ mName=%s } "
130 "End==============================",
132 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
136 NS_IMETHODIMP
PlaceholderTransaction::Merge(nsITransaction
* aOtherTransaction
,
138 if (NS_WARN_IF(!aDidMerge
) || NS_WARN_IF(!aOtherTransaction
)) {
139 return NS_ERROR_INVALID_ARG
;
142 // set out param default value
145 if (mForwardingTransaction
) {
146 MOZ_ASSERT_UNREACHABLE(
147 "tried to merge into a placeholder that was in "
149 return NS_ERROR_FAILURE
;
152 RefPtr
<EditTransactionBase
> otherTransactionBase
=
153 aOtherTransaction
->GetAsEditTransactionBase();
154 if (!otherTransactionBase
) {
155 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
156 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
157 "mName=%s } returned false due to non edit transaction",
158 this, __FUNCTION__
, aOtherTransaction
,
159 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
163 // We are absorbing all transactions if mAbsorb is lit.
165 if (CompositionTransaction
* otherCompositionTransaction
=
166 otherTransactionBase
->GetAsCompositionTransaction()) {
167 // special handling for CompositionTransaction's: they need to merge with
168 // any previous CompositionTransaction in this placeholder, if possible.
169 if (!mCompositionTransaction
) {
170 // this is the first IME txn in the placeholder
171 mCompositionTransaction
= otherCompositionTransaction
;
172 AppendChild(*otherCompositionTransaction
);
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 AppendChild(*otherCompositionTransaction
);
185 PlaceholderTransaction
* otherPlaceholderTransaction
=
186 otherTransactionBase
->GetAsPlaceholderTransaction();
187 if (!otherPlaceholderTransaction
) {
188 // See bug 171243: just drop incoming placeholders on the floor.
189 // Their children will be swallowed by this preexisting one.
190 AppendChild(*otherTransactionBase
);
194 // RememberEndingSelection();
195 // efficiency hack: no need to remember selection here, as we haven't yet
196 // finished the initial batch and we know we will be told when the batch
197 // ends. we can remeber the selection then.
201 // merge typing or IME or deletion transactions if the selection matches
203 (mName
!= nsGkAtoms::TypingTxnName
&& mName
!= nsGkAtoms::IMETxnName
&&
204 mName
!= nsGkAtoms::DeleteTxnName
)) {
205 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
206 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
207 "mName=%s } returned false due to non mergable transaction",
208 this, __FUNCTION__
, aOtherTransaction
,
209 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
213 PlaceholderTransaction
* otherPlaceholderTransaction
=
214 otherTransactionBase
->GetAsPlaceholderTransaction();
215 if (!otherPlaceholderTransaction
) {
216 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
217 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
218 "mName=%s } returned false due to non placeholder transaction",
219 this, __FUNCTION__
, aOtherTransaction
,
220 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
224 RefPtr
<nsAtom
> otherTransactionName
= otherPlaceholderTransaction
->GetName();
225 if (!otherTransactionName
|| otherTransactionName
== nsGkAtoms::_empty
||
226 otherTransactionName
!= mName
) {
227 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
228 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
229 "mName=%s } returned false due to non mergable placeholder "
231 this, __FUNCTION__
, aOtherTransaction
,
232 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
236 // check if start selection of next placeholder matches
237 // end selection of this placeholder
238 // XXX Theese checks seem wrong. The ending selection is initialized with
239 // actual Selection rather than expected Selection. Therefore, even when
240 // web apps modifies Selection, we don't merge mergable transactions.
242 // If the new transaction's starting Selection is not a caret, we shouldn't be
243 // merged with it because it's probably caused deleting the selection.
244 if (!otherPlaceholderTransaction
->mStartSel
.HasOnlyCollapsedRange()) {
245 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
246 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
247 "mName=%s } returned false due to not collapsed selection at "
248 "start of new transactions",
249 this, __FUNCTION__
, aOtherTransaction
,
250 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
254 // If our ending Selection is not a caret, we should not be merged with it
255 // because we probably changed format of a block or style of text.
256 if (!mEndSel
.HasOnlyCollapsedRange()) {
257 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
258 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
259 "mName=%s } returned false due to not collapsed selection at end "
260 "of previous transactions",
261 this, __FUNCTION__
, aOtherTransaction
,
262 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
266 // If the caret positions are now in different root nodes, e.g., the previous
267 // caret position was removed from the DOM tree, this merge should not be
269 const bool isPreviousCaretPointInSameRootOfNewCaretPoint
= [&]() {
270 nsINode
* previousRootInCurrentDOMTree
= mEndSel
.GetCommonRootNode();
271 return previousRootInCurrentDOMTree
&&
272 previousRootInCurrentDOMTree
==
273 otherPlaceholderTransaction
->mStartSel
.GetCommonRootNode();
275 if (!isPreviousCaretPointInSameRootOfNewCaretPoint
) {
276 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
277 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
278 "mName=%s } returned false due to the caret points are in "
279 "different root nodes",
280 this, __FUNCTION__
, aOtherTransaction
,
281 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
285 // If the caret points of end of us and start of new transaction are not same,
286 // we shouldn't merge them.
287 if (!otherPlaceholderTransaction
->mStartSel
.Equals(mEndSel
)) {
288 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
289 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
290 "mName=%s } returned false due to caret positions were different",
291 this, __FUNCTION__
, aOtherTransaction
,
292 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
296 mAbsorb
= true; // we need to start absorbing again
297 otherPlaceholderTransaction
->ForwardEndBatchTo(*this);
298 // AppendChild(editTransactionBase);
299 // see bug 171243: we don't need to merge placeholders
300 // into placeholders. We just reactivate merging in the
301 // pre-existing placeholder and drop the new one on the floor. The
302 // EndPlaceHolderBatch() call on the new placeholder will be
303 // forwarded to this older one.
304 DebugOnly
<nsresult
> rvIgnored
= RememberEndingSelection();
305 NS_WARNING_ASSERTION(
306 NS_SUCCEEDED(rvIgnored
),
307 "PlaceholderTransaction::RememberEndingSelection() failed, but "
310 MOZ_LOG(GetLogModule(), LogLevel::Debug
,
311 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
312 "mName=%s } returned true",
313 this, __FUNCTION__
, aOtherTransaction
,
314 nsAtomCString(mName
? mName
.get() : nsGkAtoms::_empty
).get()));
318 nsresult
PlaceholderTransaction::EndPlaceHolderBatch() {
321 if (mForwardingTransaction
) {
322 if (mForwardingTransaction
) {
323 DebugOnly
<nsresult
> rvIgnored
=
324 mForwardingTransaction
->EndPlaceHolderBatch();
325 NS_WARNING_ASSERTION(
326 NS_SUCCEEDED(rvIgnored
),
327 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored");
330 // remember our selection state.
331 nsresult rv
= RememberEndingSelection();
332 NS_WARNING_ASSERTION(
334 "PlaceholderTransaction::RememberEndingSelection() failed");
338 nsresult
PlaceholderTransaction::RememberEndingSelection() {
339 if (NS_WARN_IF(!mEditorBase
)) {
340 return NS_ERROR_NOT_INITIALIZED
;
343 RefPtr
<Selection
> selection
= mEditorBase
->GetSelection();
344 if (NS_WARN_IF(!selection
)) {
345 return NS_ERROR_FAILURE
;
347 mEndSel
.SaveSelection(*selection
);
351 } // namespace mozilla