PlaceholderTransaction.cpp (13823B)
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/dom/Selection.h" 14 #include "nsGkAtoms.h" 15 #include "nsQueryObject.h" 16 17 namespace mozilla { 18 19 using namespace dom; 20 21 PlaceholderTransaction::PlaceholderTransaction( 22 EditorBase& aEditorBase, nsStaticAtom& aName, 23 Maybe<SelectionState>&& aSelState) 24 : mEditorBase(&aEditorBase), 25 mCompositionTransaction(nullptr), 26 mStartSel(*std::move(aSelState)), 27 mAbsorb(true), 28 mCommitted(false) { 29 mName = &aName; 30 } 31 32 NS_IMPL_CYCLE_COLLECTION_CLASS(PlaceholderTransaction) 33 34 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PlaceholderTransaction, 35 EditAggregateTransaction) 36 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase); 37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStartSel); 38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSel); 39 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 40 41 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PlaceholderTransaction, 42 EditAggregateTransaction) 43 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase); 44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartSel); 45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSel); 46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 47 48 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction) 49 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction) 50 51 NS_IMPL_ADDREF_INHERITED(PlaceholderTransaction, EditAggregateTransaction) 52 NS_IMPL_RELEASE_INHERITED(PlaceholderTransaction, EditAggregateTransaction) 53 54 void PlaceholderTransaction::AppendChild(EditTransactionBase& aTransaction) { 55 mChildren.AppendElement(aTransaction); 56 } 57 58 NS_IMETHODIMP PlaceholderTransaction::DoTransaction() { 59 MOZ_LOG( 60 GetLogModule(), LogLevel::Info, 61 ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__, 62 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 63 return NS_OK; 64 } 65 66 NS_IMETHODIMP PlaceholderTransaction::UndoTransaction() { 67 MOZ_LOG(GetLogModule(), LogLevel::Info, 68 ("%p PlaceholderTransaction::%s this={ mName=%s } " 69 "Start==============================", 70 this, __FUNCTION__, 71 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 72 73 if (NS_WARN_IF(!mEditorBase)) { 74 return NS_ERROR_NOT_INITIALIZED; 75 } 76 77 // Undo transactions. 78 nsresult rv = EditAggregateTransaction::UndoTransaction(); 79 if (NS_FAILED(rv)) { 80 NS_WARNING("EditAggregateTransaction::UndoTransaction() failed"); 81 return rv; 82 } 83 84 // now restore selection 85 RefPtr<Selection> selection = mEditorBase->GetSelection(); 86 if (NS_WARN_IF(!selection)) { 87 return NS_ERROR_FAILURE; 88 } 89 rv = mStartSel.RestoreSelection(*selection); 90 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 91 "SelectionState::RestoreSelection() failed"); 92 93 MOZ_LOG(GetLogModule(), LogLevel::Info, 94 ("%p PlaceholderTransaction::%s this={ mName=%s } " 95 "End==============================", 96 this, __FUNCTION__, 97 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 98 return rv; 99 } 100 101 NS_IMETHODIMP PlaceholderTransaction::RedoTransaction() { 102 MOZ_LOG(GetLogModule(), LogLevel::Info, 103 ("%p PlaceholderTransaction::%s this={ mName=%s } " 104 "Start==============================", 105 this, __FUNCTION__, 106 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 107 108 if (NS_WARN_IF(!mEditorBase)) { 109 return NS_ERROR_NOT_INITIALIZED; 110 } 111 112 // Redo transactions. 113 nsresult rv = EditAggregateTransaction::RedoTransaction(); 114 if (NS_FAILED(rv)) { 115 NS_WARNING("EditAggregateTransaction::RedoTransaction() failed"); 116 return rv; 117 } 118 119 // now restore selection 120 RefPtr<Selection> selection = mEditorBase->GetSelection(); 121 if (NS_WARN_IF(!selection)) { 122 return NS_ERROR_FAILURE; 123 } 124 rv = mEndSel.RestoreSelection(*selection); 125 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 126 "SelectionState::RestoreSelection() failed"); 127 MOZ_LOG(GetLogModule(), LogLevel::Info, 128 ("%p PlaceholderTransaction::%s this={ mName=%s } " 129 "End==============================", 130 this, __FUNCTION__, 131 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 132 return rv; 133 } 134 135 NS_IMETHODIMP PlaceholderTransaction::Merge(nsITransaction* aOtherTransaction, 136 bool* aDidMerge) { 137 if (NS_WARN_IF(!aDidMerge) || NS_WARN_IF(!aOtherTransaction)) { 138 return NS_ERROR_INVALID_ARG; 139 } 140 141 // set out param default value 142 *aDidMerge = false; 143 144 if (mForwardingTransaction) { 145 MOZ_ASSERT_UNREACHABLE( 146 "tried to merge into a placeholder that was in " 147 "forwarding mode!"); 148 return NS_ERROR_FAILURE; 149 } 150 151 RefPtr<EditTransactionBase> otherTransactionBase = 152 aOtherTransaction->GetAsEditTransactionBase(); 153 if (!otherTransactionBase) { 154 MOZ_LOG(GetLogModule(), LogLevel::Debug, 155 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 156 "mName=%s } returned false due to non edit transaction", 157 this, __FUNCTION__, aOtherTransaction, 158 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 159 return NS_OK; 160 } 161 162 // We are absorbing all transactions if mAbsorb is lit. 163 if (mAbsorb) { 164 if (CompositionTransaction* otherCompositionTransaction = 165 otherTransactionBase->GetAsCompositionTransaction()) { 166 // special handling for CompositionTransaction's: they need to merge with 167 // any previous CompositionTransaction in this placeholder, if possible. 168 if (!mCompositionTransaction) { 169 // this is the first IME txn in the placeholder 170 mCompositionTransaction = otherCompositionTransaction; 171 AppendChild(*otherCompositionTransaction); 172 } else { 173 bool didMerge; 174 mCompositionTransaction->Merge(otherCompositionTransaction, &didMerge); 175 if (!didMerge) { 176 // it wouldn't merge. Earlier IME txn is already committed and will 177 // not absorb further IME txns. So just stack this one after it 178 // and remember it as a candidate for further merges. 179 mCompositionTransaction = otherCompositionTransaction; 180 AppendChild(*otherCompositionTransaction); 181 } 182 } 183 } else { 184 PlaceholderTransaction* otherPlaceholderTransaction = 185 otherTransactionBase->GetAsPlaceholderTransaction(); 186 if (!otherPlaceholderTransaction) { 187 // See bug 171243: just drop incoming placeholders on the floor. 188 // Their children will be swallowed by this preexisting one. 189 AppendChild(*otherTransactionBase); 190 } 191 } 192 *aDidMerge = true; 193 // RememberEndingSelection(); 194 // efficiency hack: no need to remember selection here, as we haven't yet 195 // finished the initial batch and we know we will be told when the batch 196 // ends. we can remeber the selection then. 197 return NS_OK; 198 } 199 200 // merge typing or IME or deletion transactions if the selection matches 201 if (mCommitted || 202 (mName != nsGkAtoms::TypingTxnName && mName != nsGkAtoms::IMETxnName && 203 mName != nsGkAtoms::DeleteTxnName)) { 204 MOZ_LOG(GetLogModule(), LogLevel::Debug, 205 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 206 "mName=%s } returned false due to non mergable transaction", 207 this, __FUNCTION__, aOtherTransaction, 208 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 209 return NS_OK; 210 } 211 212 PlaceholderTransaction* otherPlaceholderTransaction = 213 otherTransactionBase->GetAsPlaceholderTransaction(); 214 if (!otherPlaceholderTransaction) { 215 MOZ_LOG(GetLogModule(), LogLevel::Debug, 216 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 217 "mName=%s } returned false due to non placeholder transaction", 218 this, __FUNCTION__, aOtherTransaction, 219 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 220 return NS_OK; 221 } 222 223 RefPtr<nsAtom> otherTransactionName = otherPlaceholderTransaction->GetName(); 224 if (!otherTransactionName || otherTransactionName == nsGkAtoms::_empty || 225 otherTransactionName != mName) { 226 MOZ_LOG(GetLogModule(), LogLevel::Debug, 227 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 228 "mName=%s } returned false due to non mergable placeholder " 229 "transaction", 230 this, __FUNCTION__, aOtherTransaction, 231 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 232 return NS_OK; 233 } 234 235 // check if start selection of next placeholder matches 236 // end selection of this placeholder 237 // XXX Theese checks seem wrong. The ending selection is initialized with 238 // actual Selection rather than expected Selection. Therefore, even when 239 // web apps modifies Selection, we don't merge mergable transactions. 240 241 // If the new transaction's starting Selection is not a caret, we shouldn't be 242 // merged with it because it's probably caused deleting the selection. 243 if (!otherPlaceholderTransaction->mStartSel.HasOnlyCollapsedRange()) { 244 MOZ_LOG(GetLogModule(), LogLevel::Debug, 245 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 246 "mName=%s } returned false due to not collapsed selection at " 247 "start of new transactions", 248 this, __FUNCTION__, aOtherTransaction, 249 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 250 return NS_OK; 251 } 252 253 // If our ending Selection is not a caret, we should not be merged with it 254 // because we probably changed format of a block or style of text. 255 if (!mEndSel.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 end " 259 "of previous transactions", 260 this, __FUNCTION__, aOtherTransaction, 261 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 262 return NS_OK; 263 } 264 265 // If the caret positions are now in different root nodes, e.g., the previous 266 // caret position was removed from the DOM tree, this merge should not be 267 // done. 268 const bool isPreviousCaretPointInSameRootOfNewCaretPoint = [&]() { 269 nsINode* previousRootInCurrentDOMTree = mEndSel.GetCommonRootNode(); 270 return previousRootInCurrentDOMTree && 271 previousRootInCurrentDOMTree == 272 otherPlaceholderTransaction->mStartSel.GetCommonRootNode(); 273 }(); 274 if (!isPreviousCaretPointInSameRootOfNewCaretPoint) { 275 MOZ_LOG(GetLogModule(), LogLevel::Debug, 276 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 277 "mName=%s } returned false due to the caret points are in " 278 "different root nodes", 279 this, __FUNCTION__, aOtherTransaction, 280 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 281 return NS_OK; 282 } 283 284 // If the caret points of end of us and start of new transaction are not same, 285 // we shouldn't merge them. 286 if (!otherPlaceholderTransaction->mStartSel.Equals(mEndSel)) { 287 MOZ_LOG(GetLogModule(), LogLevel::Debug, 288 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 289 "mName=%s } returned false due to caret positions were different", 290 this, __FUNCTION__, aOtherTransaction, 291 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 292 return NS_OK; 293 } 294 295 mAbsorb = true; // we need to start absorbing again 296 otherPlaceholderTransaction->ForwardEndBatchTo(*this); 297 // AppendChild(editTransactionBase); 298 // see bug 171243: we don't need to merge placeholders 299 // into placeholders. We just reactivate merging in the 300 // pre-existing placeholder and drop the new one on the floor. The 301 // EndPlaceHolderBatch() call on the new placeholder will be 302 // forwarded to this older one. 303 DebugOnly<nsresult> rvIgnored = RememberEndingSelection(); 304 NS_WARNING_ASSERTION( 305 NS_SUCCEEDED(rvIgnored), 306 "PlaceholderTransaction::RememberEndingSelection() failed, but " 307 "ignored"); 308 *aDidMerge = true; 309 MOZ_LOG(GetLogModule(), LogLevel::Debug, 310 ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ " 311 "mName=%s } returned true", 312 this, __FUNCTION__, aOtherTransaction, 313 nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get())); 314 return NS_OK; 315 } 316 317 nsresult PlaceholderTransaction::EndPlaceHolderBatch() { 318 mAbsorb = false; 319 320 if (mForwardingTransaction) { 321 if (mForwardingTransaction) { 322 DebugOnly<nsresult> rvIgnored = 323 mForwardingTransaction->EndPlaceHolderBatch(); 324 NS_WARNING_ASSERTION( 325 NS_SUCCEEDED(rvIgnored), 326 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored"); 327 } 328 } 329 // remember our selection state. 330 nsresult rv = RememberEndingSelection(); 331 NS_WARNING_ASSERTION( 332 NS_SUCCEEDED(rv), 333 "PlaceholderTransaction::RememberEndingSelection() failed"); 334 return rv; 335 } 336 337 nsresult PlaceholderTransaction::RememberEndingSelection() { 338 if (NS_WARN_IF(!mEditorBase)) { 339 return NS_ERROR_NOT_INITIALIZED; 340 } 341 342 RefPtr<Selection> selection = mEditorBase->GetSelection(); 343 if (NS_WARN_IF(!selection)) { 344 return NS_ERROR_FAILURE; 345 } 346 mEndSel.SaveSelection(*selection); 347 return NS_OK; 348 } 349 350 } // namespace mozilla