Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / editor / libeditor / PlaceholderTransaction.cpp
blob70fe4189ea79e7b7c2bb18ec527782789e94616e
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"
8 #include <utility>
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"
18 namespace mozilla {
20 using namespace dom;
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;
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() {
60 MOZ_LOG(
61 GetLogModule(), LogLevel::Info,
62 ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__,
63 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
64 return NS_OK;
67 NS_IMETHODIMP PlaceholderTransaction::UndoTransaction() {
68 MOZ_LOG(GetLogModule(), LogLevel::Info,
69 ("%p PlaceholderTransaction::%s this={ mName=%s } "
70 "Start==============================",
71 this, __FUNCTION__,
72 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
74 if (NS_WARN_IF(!mEditorBase)) {
75 return NS_ERROR_NOT_INITIALIZED;
78 // Undo transactions.
79 nsresult rv = EditAggregateTransaction::UndoTransaction();
80 if (NS_FAILED(rv)) {
81 NS_WARNING("EditAggregateTransaction::UndoTransaction() failed");
82 return rv;
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==============================",
97 this, __FUNCTION__,
98 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
99 return rv;
102 NS_IMETHODIMP PlaceholderTransaction::RedoTransaction() {
103 MOZ_LOG(GetLogModule(), LogLevel::Info,
104 ("%p PlaceholderTransaction::%s this={ mName=%s } "
105 "Start==============================",
106 this, __FUNCTION__,
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();
115 if (NS_FAILED(rv)) {
116 NS_WARNING("EditAggregateTransaction::RedoTransaction() failed");
117 return rv;
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==============================",
131 this, __FUNCTION__,
132 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
133 return rv;
136 NS_IMETHODIMP PlaceholderTransaction::Merge(nsITransaction* aOtherTransaction,
137 bool* aDidMerge) {
138 if (NS_WARN_IF(!aDidMerge) || NS_WARN_IF(!aOtherTransaction)) {
139 return NS_ERROR_INVALID_ARG;
142 // set out param default value
143 *aDidMerge = false;
145 if (mForwardingTransaction) {
146 MOZ_ASSERT_UNREACHABLE(
147 "tried to merge into a placeholder that was in "
148 "forwarding mode!");
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()));
160 return NS_OK;
163 // We are absorbing all transactions if mAbsorb is lit.
164 if (mAbsorb) {
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);
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 AppendChild(*otherCompositionTransaction);
184 } else {
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);
193 *aDidMerge = true;
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.
198 return NS_OK;
201 // merge typing or IME or deletion transactions if the selection matches
202 if (mCommitted ||
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()));
210 return NS_OK;
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()));
221 return NS_OK;
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 "
230 "transaction",
231 this, __FUNCTION__, aOtherTransaction,
232 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
233 return NS_OK;
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()));
251 return NS_OK;
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()));
263 return NS_OK;
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
268 // done.
269 const bool isPreviousCaretPointInSameRootOfNewCaretPoint = [&]() {
270 nsINode* previousRootInCurrentDOMTree = mEndSel.GetCommonRootNode();
271 return previousRootInCurrentDOMTree &&
272 previousRootInCurrentDOMTree ==
273 otherPlaceholderTransaction->mStartSel.GetCommonRootNode();
274 }();
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()));
282 return NS_OK;
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()));
293 return NS_OK;
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 "
308 "ignored");
309 *aDidMerge = true;
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()));
315 return NS_OK;
318 nsresult PlaceholderTransaction::EndPlaceHolderBatch() {
319 mAbsorb = false;
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(
333 NS_SUCCEEDED(rv),
334 "PlaceholderTransaction::RememberEndingSelection() failed");
335 return rv;
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);
348 return NS_OK;
351 } // namespace mozilla