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/. */
5
6 #include "PlaceholderTransaction.h"
7
8 #include <utility>
9
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"
17
18 namespace mozilla {
19
20 using namespace dom;
21
PlaceholderTransaction(EditorBase & aEditorBase,nsStaticAtom & aName,Maybe<SelectionState> && aSelState)22 PlaceholderTransaction::PlaceholderTransaction(
23 EditorBase& aEditorBase, nsStaticAtom& aName,
24 Maybe<SelectionState>&& aSelState)
25 : mEditorBase(&aEditorBase),
26 mCompositionTransaction(nullptr),
27 mStartSel(*std::move(aSelState)),
28 mAbsorb(true),
29 mCommitted(false) {
30 mName = &aName;
31 }
32
33 NS_IMPL_CYCLE_COLLECTION_CLASS(PlaceholderTransaction)
34
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
41
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
48
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction)49 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction)
50 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
51
52 NS_IMPL_ADDREF_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
53 NS_IMPL_RELEASE_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
54
55 NS_IMETHODIMP PlaceholderTransaction::DoTransaction() {
56 MOZ_LOG(
57 GetLogModule(), LogLevel::Info,
58 ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__,
59 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
60 return NS_OK;
61 }
62
UndoTransaction()63 NS_IMETHODIMP PlaceholderTransaction::UndoTransaction() {
64 MOZ_LOG(GetLogModule(), LogLevel::Info,
65 ("%p PlaceholderTransaction::%s this={ mName=%s } "
66 "Start==============================",
67 this, __FUNCTION__,
68 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
69
70 if (NS_WARN_IF(!mEditorBase)) {
71 return NS_ERROR_NOT_INITIALIZED;
72 }
73
74 // Undo transactions.
75 nsresult rv = EditAggregateTransaction::UndoTransaction();
76 if (NS_FAILED(rv)) {
77 NS_WARNING("EditAggregateTransaction::UndoTransaction() failed");
78 return rv;
79 }
80
81 // now restore selection
82 RefPtr<Selection> selection = mEditorBase->GetSelection();
83 if (NS_WARN_IF(!selection)) {
84 return NS_ERROR_FAILURE;
85 }
86 rv = mStartSel.RestoreSelection(*selection);
87 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
88 "SelectionState::RestoreSelection() failed");
89
90 MOZ_LOG(GetLogModule(), LogLevel::Info,
91 ("%p PlaceholderTransaction::%s this={ mName=%s } "
92 "End==============================",
93 this, __FUNCTION__,
94 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
95 return rv;
96 }
97
RedoTransaction()98 NS_IMETHODIMP PlaceholderTransaction::RedoTransaction() {
99 MOZ_LOG(GetLogModule(), LogLevel::Info,
100 ("%p PlaceholderTransaction::%s this={ mName=%s } "
101 "Start==============================",
102 this, __FUNCTION__,
103 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
104
105 if (NS_WARN_IF(!mEditorBase)) {
106 return NS_ERROR_NOT_INITIALIZED;
107 }
108
109 // Redo transactions.
110 nsresult rv = EditAggregateTransaction::RedoTransaction();
111 if (NS_FAILED(rv)) {
112 NS_WARNING("EditAggregateTransaction::RedoTransaction() failed");
113 return rv;
114 }
115
116 // now restore selection
117 RefPtr<Selection> selection = mEditorBase->GetSelection();
118 if (NS_WARN_IF(!selection)) {
119 return NS_ERROR_FAILURE;
120 }
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==============================",
127 this, __FUNCTION__,
128 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
129 return rv;
130 }
131
Merge(nsITransaction * aOtherTransaction,bool * aDidMerge)132 NS_IMETHODIMP PlaceholderTransaction::Merge(nsITransaction* aOtherTransaction,
133 bool* aDidMerge) {
134 if (NS_WARN_IF(!aDidMerge) || NS_WARN_IF(!aOtherTransaction)) {
135 return NS_ERROR_INVALID_ARG;
136 }
137
138 // set out param default value
139 *aDidMerge = false;
140
141 if (mForwardingTransaction) {
142 MOZ_ASSERT_UNREACHABLE(
143 "tried to merge into a placeholder that was in "
144 "forwarding mode!");
145 return NS_ERROR_FAILURE;
146 }
147
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()));
156 return NS_OK;
157 }
158
159 // We are absorbing all transactions if mAbsorb is lit.
160 if (mAbsorb) {
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");
173 } else {
174 bool didMerge;
175 mCompositionTransaction->Merge(otherCompositionTransaction, &didMerge);
176 if (!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");
186 }
187 }
188 } else {
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");
198 }
199 }
200 *aDidMerge = true;
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.
205 return NS_OK;
206 }
207
208 // merge typing or IME or deletion transactions if the selection matches
209 if (mCommitted ||
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()));
217 return NS_OK;
218 }
219
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()));
228 return NS_OK;
229 }
230
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 "
241 "transaction",
242 this, __FUNCTION__, aOtherTransaction,
243 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
244 return NS_OK;
245 }
246 // check if start selection of next placeholder matches
247 // end selection of this placeholder
248 if (!otherPlaceholderTransaction->StartSelectionEquals(mEndSel)) {
249 MOZ_LOG(GetLogModule(), LogLevel::Debug,
250 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
251 "mName=%s } returned false due to selection difference",
252 this, __FUNCTION__, aOtherTransaction,
253 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
254 return NS_OK;
255 }
256 mAbsorb = true; // we need to start absorbing again
257 otherPlaceholderTransaction->ForwardEndBatchTo(*this);
258 // AppendChild(editTransactionBase);
259 // see bug 171243: we don't need to merge placeholders
260 // into placeholders. We just reactivate merging in the
261 // pre-existing placeholder and drop the new one on the floor. The
262 // EndPlaceHolderBatch() call on the new placeholder will be
263 // forwarded to this older one.
264 rvIgnored = RememberEndingSelection();
265 NS_WARNING_ASSERTION(
266 NS_SUCCEEDED(rvIgnored),
267 "PlaceholderTransaction::RememberEndingSelection() failed, but "
268 "ignored");
269 *aDidMerge = true;
270 MOZ_LOG(GetLogModule(), LogLevel::Debug,
271 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
272 "mName=%s } returned true",
273 this, __FUNCTION__, aOtherTransaction,
274 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
275 return NS_OK;
276 }
277
StartSelectionEquals(SelectionState & aSelectionState)278 bool PlaceholderTransaction::StartSelectionEquals(
279 SelectionState& aSelectionState) {
280 // determine if starting selection matches the given selection state.
281 // note that we only care about collapsed selections.
282 return mStartSel.IsCollapsed() && aSelectionState.IsCollapsed() &&
283 mStartSel.Equals(aSelectionState);
284 }
285
EndPlaceHolderBatch()286 nsresult PlaceholderTransaction::EndPlaceHolderBatch() {
287 mAbsorb = false;
288
289 if (mForwardingTransaction) {
290 if (mForwardingTransaction) {
291 DebugOnly<nsresult> rvIgnored =
292 mForwardingTransaction->EndPlaceHolderBatch();
293 NS_WARNING_ASSERTION(
294 NS_SUCCEEDED(rvIgnored),
295 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored");
296 }
297 }
298 // remember our selection state.
299 nsresult rv = RememberEndingSelection();
300 NS_WARNING_ASSERTION(
301 NS_SUCCEEDED(rv),
302 "PlaceholderTransaction::RememberEndingSelection() failed");
303 return rv;
304 }
305
RememberEndingSelection()306 nsresult PlaceholderTransaction::RememberEndingSelection() {
307 if (NS_WARN_IF(!mEditorBase)) {
308 return NS_ERROR_NOT_INITIALIZED;
309 }
310
311 RefPtr<Selection> selection = mEditorBase->GetSelection();
312 if (NS_WARN_IF(!selection)) {
313 return NS_ERROR_FAILURE;
314 }
315 mEndSel.SaveSelection(*selection);
316 return NS_OK;
317 }
318
319 } // namespace mozilla
320