SplitNodeTransaction.cpp (9136B)
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 "SplitNodeTransaction.h" 7 8 #include "EditorDOMPoint.h" // for EditorRawDOMPoint 9 #include "HTMLEditHelpers.h" // for SplitNodeResult 10 #include "HTMLEditor.h" // for HTMLEditor 11 #include "HTMLEditorInlines.h" 12 #include "HTMLEditUtils.h" 13 #include "SelectionState.h" // for AutoTrackDOMPoint and RangeUpdater 14 15 #include "mozilla/Logging.h" 16 #include "mozilla/ToString.h" 17 #include "nsAString.h" 18 #include "nsDebug.h" // for NS_ASSERTION, etc. 19 #include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc. 20 #include "nsIContent.h" // for nsIContent 21 22 namespace mozilla { 23 24 using namespace dom; 25 26 template already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create( 27 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aStartOfRightContent); 28 template already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create( 29 HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aStartOfRightContent); 30 31 // static 32 template <typename PT, typename CT> 33 already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create( 34 HTMLEditor& aHTMLEditor, 35 const EditorDOMPointBase<PT, CT>& aStartOfRightContent) { 36 RefPtr<SplitNodeTransaction> transaction = 37 new SplitNodeTransaction(aHTMLEditor, aStartOfRightContent); 38 return transaction.forget(); 39 } 40 41 template <typename PT, typename CT> 42 SplitNodeTransaction::SplitNodeTransaction( 43 HTMLEditor& aHTMLEditor, 44 const EditorDOMPointBase<PT, CT>& aStartOfRightContent) 45 : mHTMLEditor(&aHTMLEditor), 46 mSplitContent(aStartOfRightContent.template GetContainerAs<nsIContent>()), 47 mSplitOffset(aStartOfRightContent.Offset()) { 48 // printf("SplitNodeTransaction size: %zu\n", sizeof(SplitNodeTransaction)); 49 static_assert(sizeof(SplitNodeTransaction) <= 64, 50 "Transaction classes may be created a lot and may be alive " 51 "long so that keep the foot print smaller as far as possible"); 52 MOZ_DIAGNOSTIC_ASSERT(aStartOfRightContent.IsInContentNode()); 53 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsSplittableNode( 54 *aStartOfRightContent.template ContainerAs<nsIContent>())); 55 } 56 57 std::ostream& operator<<(std::ostream& aStream, 58 const SplitNodeTransaction& aTransaction) { 59 aStream << "{ mParentNode=" << aTransaction.mParentNode.get(); 60 if (aTransaction.mParentNode) { 61 aStream << " (" << *aTransaction.mParentNode << ")"; 62 } 63 aStream << ", mNewContent=" << aTransaction.mNewContent.get(); 64 if (aTransaction.mNewContent) { 65 aStream << " (" << *aTransaction.mNewContent << ")"; 66 } 67 aStream << ", mSplitContent=" << aTransaction.mSplitContent.get(); 68 if (aTransaction.mSplitContent) { 69 aStream << " (" << *aTransaction.mSplitContent << ")"; 70 } 71 aStream << ", mSplitOffset=" << aTransaction.mSplitOffset 72 << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }"; 73 return aStream; 74 } 75 76 NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase, 77 mHTMLEditor, mParentNode, mSplitContent, 78 mNewContent) 79 80 NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction, EditTransactionBase) 81 NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction, EditTransactionBase) 82 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction) 83 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) 84 85 NS_IMETHODIMP SplitNodeTransaction::DoTransaction() { 86 MOZ_LOG(GetLogModule(), LogLevel::Info, 87 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__, 88 ToString(*this).c_str())); 89 90 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mSplitContent))) { 91 return NS_ERROR_NOT_AVAILABLE; 92 } 93 MOZ_ASSERT(mSplitOffset <= mSplitContent->Length()); 94 95 // Create a new node 96 IgnoredErrorResult error; 97 // Don't use .downcast directly because AsContent has an assertion we want 98 nsCOMPtr<nsINode> newNode = mSplitContent->CloneNode(false, error); 99 if (MOZ_UNLIKELY(error.Failed())) { 100 NS_WARNING("nsINode::CloneNode() failed"); 101 return error.StealNSResult(); 102 } 103 if (MOZ_UNLIKELY(NS_WARN_IF(!newNode))) { 104 return NS_ERROR_UNEXPECTED; 105 } 106 107 mNewContent = newNode->AsContent(); 108 mParentNode = mSplitContent->GetParentNode(); 109 if (!mParentNode) { 110 NS_WARNING("The splitting content was an orphan node"); 111 return NS_ERROR_NOT_AVAILABLE; 112 } 113 114 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 115 const OwningNonNull<nsIContent> splittingContent = *mSplitContent; 116 // MOZ_KnownLive(*mNewContent): it's grabbed by newNode 117 Result<SplitNodeResult, nsresult> splitNodeResult = DoTransactionInternal( 118 htmlEditor, splittingContent, MOZ_KnownLive(*mNewContent), mSplitOffset); 119 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 120 NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed"); 121 return EditorBase::ToGenericNSResult(splitNodeResult.unwrapErr()); 122 } 123 // The user should handle selection rather here. 124 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 125 return NS_OK; 126 } 127 128 Result<SplitNodeResult, nsresult> SplitNodeTransaction::DoTransactionInternal( 129 HTMLEditor& aHTMLEditor, nsIContent& aSplittingContent, 130 nsIContent& aNewContent, uint32_t aSplitOffset) { 131 if (Element* const splittingElement = Element::FromNode(aSplittingContent)) { 132 // MOZ_KnownLive(*splittingElement): aSplittingContent should be grabbed by 133 // the callers. 134 nsresult rv = 135 aHTMLEditor.MarkElementDirty(MOZ_KnownLive(*splittingElement)); 136 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 137 return Err(NS_ERROR_EDITOR_DESTROYED); 138 } 139 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 140 "EditorBase::MarkElementDirty() failed, but ignored"); 141 } 142 143 Result<SplitNodeResult, nsresult> splitNodeResult = aHTMLEditor.DoSplitNode( 144 EditorDOMPoint(&aSplittingContent, 145 std::min(aSplitOffset, aSplittingContent.Length())), 146 aNewContent); 147 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 148 NS_WARNING("HTMLEditor::DoSplitNode() failed"); 149 return splitNodeResult; 150 } 151 // When adding caret suggestion to SplitNodeResult, here didn't change 152 // selection so that just ignore it. 153 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 154 return splitNodeResult; 155 } 156 157 NS_IMETHODIMP SplitNodeTransaction::UndoTransaction() { 158 MOZ_LOG(GetLogModule(), LogLevel::Info, 159 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__, 160 ToString(*this).c_str())); 161 162 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mNewContent) || 163 NS_WARN_IF(!mParentNode) || NS_WARN_IF(!mSplitContent) || 164 NS_WARN_IF(mNewContent->IsBeingRemoved()))) { 165 return NS_ERROR_NOT_AVAILABLE; 166 } 167 168 // This assumes Do inserted the new node in front of the prior existing node 169 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 170 const OwningNonNull<nsIContent> keepingContent = *mSplitContent; 171 const OwningNonNull<nsIContent> removingContent = *mNewContent; 172 EditorDOMPoint joinedPoint; 173 // Unfortunately, we cannot track joining point if moving right node content 174 // into left node since it cannot track changes from web apps and HTMLEditor 175 // never removes the content of the left node. So it should be true that 176 // we don't need to track the point in this case. 177 nsresult rv = htmlEditor->DoJoinNodes(keepingContent, removingContent); 178 if (NS_SUCCEEDED(rv)) { 179 // Adjust split offset for redo here 180 if (joinedPoint.IsSet()) { 181 mSplitOffset = joinedPoint.Offset(); 182 } 183 } else { 184 NS_WARNING("HTMLEditor::DoJoinNodes() failed"); 185 } 186 return rv; 187 } 188 189 /* Redo cannot simply resplit the right node, because subsequent transactions 190 * on the redo stack may depend on the left node existing in its previous 191 * state. 192 */ 193 NS_IMETHODIMP SplitNodeTransaction::RedoTransaction() { 194 MOZ_LOG(GetLogModule(), LogLevel::Info, 195 ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__, 196 ToString(*this).c_str())); 197 198 if (MOZ_UNLIKELY(NS_WARN_IF(!mNewContent) || NS_WARN_IF(!mParentNode) || 199 NS_WARN_IF(!mSplitContent) || NS_WARN_IF(!mHTMLEditor))) { 200 return NS_ERROR_NOT_AVAILABLE; 201 } 202 203 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 204 const OwningNonNull<nsIContent> newContent = *mNewContent; 205 const OwningNonNull<nsIContent> splittingContent = *mSplitContent; 206 Result<SplitNodeResult, nsresult> splitNodeResult = DoTransactionInternal( 207 htmlEditor, splittingContent, newContent, mSplitOffset); 208 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 209 NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed"); 210 return EditorBase::ToGenericNSResult(splitNodeResult.unwrapErr()); 211 } 212 // When adding caret suggestion to SplitNodeResult, here didn't change 213 // selection so that just ignore it. 214 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 215 return NS_OK; 216 } 217 218 } // namespace mozilla