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