MoveNodeTransaction.cpp (13301B)
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 "MoveNodeTransaction.h" 7 8 #include "EditorBase.h" // for EditorBase 9 #include "EditorDOMAPIWrapper.h" // for AutoNodeAPIWrapper 10 #include "EditorDOMPoint.h" // for EditorDOMPoint 11 #include "HTMLEditor.h" // for HTMLEditor 12 #include "HTMLEditUtils.h" // for HTMLEditUtils 13 14 #include "mozilla/Likely.h" 15 #include "mozilla/Logging.h" 16 #include "mozilla/ToString.h" 17 18 #include "nsDebug.h" // for NS_WARNING, etc. 19 #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. 20 #include "nsIContent.h" // for nsIContent 21 #include "nsString.h" // for nsString 22 23 namespace mozilla { 24 25 using namespace dom; 26 27 /****************************************************************************** 28 * mozilla::MoveNodeTransactionBase 29 ******************************************************************************/ 30 31 MoveNodeTransactionBase::MoveNodeTransactionBase( 32 HTMLEditor& aHTMLEditor, nsIContent& aLastContentToMove, 33 const EditorRawDOMPoint& aPointToInsert) 34 : mContainer(aPointToInsert.GetContainer()), 35 mReference(aPointToInsert.GetChild()), 36 mOldContainer(aLastContentToMove.GetParentNode()), 37 mOldNextSibling(aLastContentToMove.GetNextSibling()), 38 mHTMLEditor(&aHTMLEditor) { 39 MOZ_ASSERT(mContainer); 40 MOZ_ASSERT(mOldContainer); 41 MOZ_ASSERT_IF(mReference, mReference->GetParentNode() == mContainer); 42 MOZ_ASSERT_IF(mOldNextSibling, 43 mOldNextSibling->GetParentNode() == mOldContainer); 44 } 45 46 NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveNodeTransactionBase, EditTransactionBase, 47 mHTMLEditor, mContainer, mReference, 48 mOldContainer, mOldNextSibling) 49 50 NS_IMPL_ADDREF_INHERITED(MoveNodeTransactionBase, EditTransactionBase) 51 NS_IMPL_RELEASE_INHERITED(MoveNodeTransactionBase, EditTransactionBase) 52 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveNodeTransactionBase) 53 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) 54 55 /****************************************************************************** 56 * mozilla::MoveNodeTransaction 57 ******************************************************************************/ 58 59 template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( 60 HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, 61 const EditorDOMPoint& aPointToInsert); 62 template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( 63 HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, 64 const EditorRawDOMPoint& aPointToInsert); 65 66 // static 67 template <typename PT, typename CT> 68 already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( 69 HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, 70 const EditorDOMPointBase<PT, CT>& aPointToInsert) { 71 if (NS_WARN_IF(!aContentToMove.GetParentNode()) || 72 NS_WARN_IF(!aPointToInsert.IsSet())) { 73 return nullptr; 74 } 75 // TODO: We should not allow to move a node to improper container element. 76 // However, this is currently used to move invalid parent while 77 // processing the nodes. Therefore, treating the case as error breaks 78 // a lot. 79 if (NS_WARN_IF(aContentToMove.IsInComposedDoc() && 80 !HTMLEditUtils::IsRemovableNode(aContentToMove)) || 81 // The destination should be editable, but it may be in an orphan node or 82 // sub-tree to reduce number of DOM mutation events. In such case, we're 83 // okay to move a node into the non-editable content because we can assume 84 // that the caller will insert it into an editable element. 85 NS_WARN_IF(aPointToInsert.IsInComposedDoc() && 86 !HTMLEditUtils::IsSimplyEditableNode( 87 *aPointToInsert.GetContainer()))) { 88 return nullptr; 89 } 90 RefPtr<MoveNodeTransaction> transaction = 91 new MoveNodeTransaction(aHTMLEditor, aContentToMove, aPointToInsert); 92 return transaction.forget(); 93 } 94 95 template <typename PT, typename CT> 96 MoveNodeTransaction::MoveNodeTransaction( 97 HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, 98 const EditorDOMPointBase<PT, CT>& aPointToInsert) 99 : MoveNodeTransactionBase(aHTMLEditor, aContentToMove, 100 aPointToInsert.template To<EditorRawDOMPoint>()), 101 mContentToMove(&aContentToMove) {} 102 103 std::ostream& operator<<(std::ostream& aStream, 104 const MoveNodeTransaction& aTransaction) { 105 auto DumpNodeDetails = [&](const nsINode* aNode) { 106 if (aNode) { 107 if (aNode->IsText()) { 108 nsAutoString data; 109 aNode->AsText()->GetData(data); 110 aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")"; 111 } else { 112 aStream << " (" << *aNode << ")"; 113 } 114 } 115 }; 116 aStream << "{ mContentToMove=" << aTransaction.mContentToMove.get(); 117 DumpNodeDetails(aTransaction.mContentToMove); 118 aStream << ", mContainer=" << aTransaction.mContainer.get(); 119 DumpNodeDetails(aTransaction.mContainer); 120 aStream << ", mReference=" << aTransaction.mReference.get(); 121 DumpNodeDetails(aTransaction.mReference); 122 aStream << ", mOldContainer=" << aTransaction.mOldContainer.get(); 123 DumpNodeDetails(aTransaction.mOldContainer); 124 aStream << ", mOldNextSibling=" << aTransaction.mOldNextSibling.get(); 125 DumpNodeDetails(aTransaction.mOldNextSibling); 126 aStream << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }"; 127 return aStream; 128 } 129 130 NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase, 131 mContentToMove) 132 133 NS_IMPL_ADDREF_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase) 134 NS_IMPL_RELEASE_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase) 135 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveNodeTransaction) 136 NS_INTERFACE_MAP_END_INHERITING(MoveNodeTransactionBase) 137 138 NS_IMETHODIMP MoveNodeTransaction::DoTransaction() { 139 MOZ_LOG(GetLogModule(), LogLevel::Info, 140 ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, 141 ToString(*this).c_str())); 142 return DoTransactionInternal(); 143 } 144 145 nsresult MoveNodeTransaction::DoTransactionInternal() { 146 MOZ_DIAGNOSTIC_ASSERT(mHTMLEditor); 147 MOZ_DIAGNOSTIC_ASSERT(mContentToMove); 148 MOZ_DIAGNOSTIC_ASSERT(mContainer); 149 MOZ_DIAGNOSTIC_ASSERT(mOldContainer); 150 151 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 152 const OwningNonNull<nsIContent> contentToMove = *mContentToMove; 153 const OwningNonNull<nsINode> container = *mContainer; 154 const nsCOMPtr<nsIContent> newNextSibling = mReference; 155 if (contentToMove->IsElement()) { 156 nsresult rv = htmlEditor->MarkElementDirty( 157 MOZ_KnownLive(*contentToMove->AsElement())); 158 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 159 return EditorBase::ToGenericNSResult(rv); 160 } 161 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 162 "EditorBase::MarkElementDirty() failed, but ignored"); 163 } 164 165 { 166 AutoMoveNodeSelNotify notifyStoredRanges( 167 htmlEditor->RangeUpdaterRef(), contentToMove, 168 newNextSibling ? EditorRawDOMPoint(newNextSibling) 169 : EditorRawDOMPoint::AtEndOf(*container)); 170 AutoNodeAPIWrapper nodeWrapper(htmlEditor, container); 171 nsresult rv = nodeWrapper.InsertBefore(contentToMove, newNextSibling); 172 if (NS_FAILED(rv)) { 173 NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed"); 174 return rv; 175 } 176 NS_WARNING_ASSERTION(nodeWrapper.IsExpectedResult(), 177 "Moving node caused other mutations, but ignored"); 178 if (MOZ_LIKELY(contentToMove->GetParentNode() && 179 contentToMove->OwnerDoc() == htmlEditor->GetDocument())) { 180 notifyStoredRanges.DidMoveContent(contentToMove); 181 } 182 } 183 184 return NS_OK; 185 } 186 187 NS_IMETHODIMP MoveNodeTransaction::UndoTransaction() { 188 MOZ_LOG(GetLogModule(), LogLevel::Info, 189 ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, 190 ToString(*this).c_str())); 191 192 if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) || 193 NS_WARN_IF(!mOldContainer)) { 194 // Perhaps, nulled-out by the cycle collector. 195 return NS_ERROR_FAILURE; 196 } 197 198 // If the original point has been changed, refer mOldNextSibling if it's 199 // reasonable. Otherwise, use end of the old container. 200 if (mOldNextSibling && mOldContainer != mOldNextSibling->GetParentNode()) { 201 // TODO: Check whether the new container is proper one for containing 202 // mContentToMove. However, there are few testcases so that we 203 // shouldn't change here without creating a lot of undo tests. 204 if (mOldNextSibling->GetParentNode() && 205 (mOldNextSibling->IsInComposedDoc() || 206 !mOldContainer->IsInComposedDoc())) { 207 mOldContainer = mOldNextSibling->GetParentNode(); 208 } else { 209 mOldNextSibling = nullptr; // end of mOldContainer 210 } 211 } 212 213 if (MOZ_UNLIKELY(mOldContainer->IsInComposedDoc() && 214 !HTMLEditUtils::IsSimplyEditableNode(*mOldContainer))) { 215 NS_WARNING( 216 "MoveNodeTransaction::UndoTransaction() couldn't move the " 217 "content into the old container due to non-editable one"); 218 return NS_ERROR_FAILURE; 219 } 220 if (MOZ_UNLIKELY(mContentToMove->IsInComposedDoc() && 221 !HTMLEditUtils::IsRemovableNode(*mContentToMove))) { 222 // For the consistency with MoveSiblingsTransaction::UndoTransaction(), we 223 // should return "OK" even if we cannot move the node from non-editable 224 // node. 225 return NS_OK; 226 } 227 228 // And store the latest node which should be referred at redoing. 229 mContainer = mContentToMove->GetParentNode(); 230 mReference = mContentToMove->GetNextSibling(); 231 232 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 233 const OwningNonNull<nsINode> oldContainer = *mOldContainer; 234 const OwningNonNull<nsIContent> contentToMove = *mContentToMove; 235 const nsCOMPtr<nsIContent> oldNextSibling = mOldNextSibling; 236 if (contentToMove->IsElement()) { 237 nsresult rv = htmlEditor->MarkElementDirty( 238 MOZ_KnownLive(*contentToMove->AsElement())); 239 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 240 return EditorBase::ToGenericNSResult(rv); 241 } 242 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 243 "EditorBase::MarkElementDirty() failed, but ignored"); 244 } 245 246 { 247 AutoMoveNodeSelNotify notifyStoredRanges( 248 htmlEditor->RangeUpdaterRef(), contentToMove, 249 oldNextSibling ? EditorRawDOMPoint(oldNextSibling) 250 : EditorRawDOMPoint::AtEndOf(*oldContainer)); 251 AutoNodeAPIWrapper nodeWrapper(htmlEditor, oldContainer); 252 nsresult rv = nodeWrapper.InsertBefore(contentToMove, oldNextSibling); 253 if (NS_FAILED(rv)) { 254 NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed"); 255 return rv; 256 } 257 NS_WARNING_ASSERTION(nodeWrapper.IsExpectedResult(), 258 "Moving node caused other mutations, but ignored"); 259 if (MOZ_LIKELY(contentToMove->GetParentNode() && 260 contentToMove->OwnerDoc() == htmlEditor->GetDocument())) { 261 notifyStoredRanges.DidMoveContent(contentToMove); 262 } 263 } 264 265 return NS_OK; 266 } 267 268 NS_IMETHODIMP MoveNodeTransaction::RedoTransaction() { 269 MOZ_LOG(GetLogModule(), LogLevel::Info, 270 ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, 271 ToString(*this).c_str())); 272 273 if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) || 274 NS_WARN_IF(!mContainer)) { 275 // Perhaps, nulled-out by the cycle collector. 276 return NS_ERROR_FAILURE; 277 } 278 279 // If the inserting point has been changed, refer mReference if it's 280 // reasonable. Otherwise, use end of the container. 281 if (mReference && mContainer != mReference->GetParentNode()) { 282 // TODO: Check whether the new container is proper one for containing 283 // mContentToMove. However, there are few testcases so that we 284 // shouldn't change here without creating a lot of redo tests. 285 if (mReference->GetParentNode() && 286 (mReference->IsInComposedDoc() || !mContainer->IsInComposedDoc())) { 287 mContainer = mReference->GetParentNode(); 288 } else { 289 mReference = nullptr; // end of mContainer 290 } 291 } 292 293 if (MOZ_UNLIKELY(mContainer->IsInComposedDoc() && 294 !HTMLEditUtils::IsSimplyEditableNode(*mContainer))) { 295 NS_WARNING( 296 "MoveNodeTransaction::RedoTransaction() couldn't move the " 297 "content into the new container due to non-editable one"); 298 return NS_ERROR_FAILURE; 299 } 300 if (NS_WARN_IF(mContentToMove->IsInComposedDoc() && 301 !HTMLEditUtils::IsRemovableNode(*mContentToMove))) { 302 // For the consistency with MoveSiblingsTransaction::RedoTransaction(), we 303 // should return "OK" even if we cannot move the node from non-editable 304 // node. 305 return NS_OK; 306 } 307 308 // And store the latest node which should be back. 309 mOldContainer = mContentToMove->GetParentNode(); 310 mOldNextSibling = mContentToMove->GetNextSibling(); 311 312 nsresult rv = DoTransactionInternal(); 313 if (NS_FAILED(rv)) { 314 NS_WARNING("MoveNodeTransaction::DoTransactionInternal() failed"); 315 return rv; 316 } 317 return NS_OK; 318 } 319 320 } // namespace mozilla