JoinNodesTransaction.cpp (7346B)
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 "JoinNodesTransaction.h" 7 8 #include "EditorDOMPoint.h" // for EditorDOMPoint, etc. 9 #include "HTMLEditHelpers.h" // for SplitNodeResult 10 #include "HTMLEditor.h" // for HTMLEditor 11 #include "HTMLEditorInlines.h" 12 #include "HTMLEditUtils.h" 13 14 #include "mozilla/Logging.h" 15 #include "mozilla/ToString.h" 16 #include "mozilla/dom/Text.h" 17 18 #include "nsAString.h" 19 #include "nsDebug.h" // for NS_ASSERTION, etc. 20 #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. 21 #include "nsIContent.h" // for nsIContent 22 #include "nsISupportsImpl.h" // for QueryInterface, etc. 23 24 namespace mozilla { 25 26 using namespace dom; 27 28 // static 29 already_AddRefed<JoinNodesTransaction> JoinNodesTransaction::MaybeCreate( 30 HTMLEditor& aHTMLEditor, nsIContent& aLeftContent, 31 nsIContent& aRightContent) { 32 RefPtr<JoinNodesTransaction> transaction = 33 new JoinNodesTransaction(aHTMLEditor, aLeftContent, aRightContent); 34 if (NS_WARN_IF(!transaction->CanDoIt())) { 35 return nullptr; 36 } 37 return transaction.forget(); 38 } 39 40 JoinNodesTransaction::JoinNodesTransaction(HTMLEditor& aHTMLEditor, 41 nsIContent& aLeftContent, 42 nsIContent& aRightContent) 43 : mHTMLEditor(&aHTMLEditor), 44 mRemovedContent(&aRightContent), 45 mKeepingContent(&aLeftContent) { 46 // printf("JoinNodesTransaction size: %zu\n", sizeof(JoinNodesTransaction)); 47 static_assert(sizeof(JoinNodesTransaction) <= 64, 48 "Transaction classes may be created a lot and may be alive " 49 "long so that keep the foot print smaller as far as possible"); 50 } 51 52 std::ostream& operator<<(std::ostream& aStream, 53 const JoinNodesTransaction& aTransaction) { 54 aStream << "{ mParentNode=" << aTransaction.mParentNode.get(); 55 if (aTransaction.mParentNode) { 56 aStream << " (" << *aTransaction.mParentNode << ")"; 57 } 58 aStream << ", mRemovedContent=" << aTransaction.mRemovedContent.get(); 59 if (aTransaction.mRemovedContent) { 60 aStream << " (" << *aTransaction.mRemovedContent << ")"; 61 } 62 aStream << ", mKeepingContent=" << aTransaction.mKeepingContent.get(); 63 if (aTransaction.mKeepingContent) { 64 aStream << " (" << *aTransaction.mKeepingContent << ")"; 65 } 66 aStream << ", mJoinedOffset=" << aTransaction.mJoinedOffset 67 << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }"; 68 return aStream; 69 } 70 71 NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodesTransaction, EditTransactionBase, 72 mHTMLEditor, mParentNode, mRemovedContent, 73 mKeepingContent) 74 75 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodesTransaction) 76 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) 77 78 bool JoinNodesTransaction::CanDoIt() const { 79 if (NS_WARN_IF(!mKeepingContent) || NS_WARN_IF(!mRemovedContent) || 80 NS_WARN_IF(!mHTMLEditor) || 81 NS_WARN_IF(mRemovedContent->IsBeingRemoved()) || 82 !mKeepingContent->IsInComposedDoc()) { 83 return false; 84 } 85 return HTMLEditUtils::IsRemovableFromParentNode(*mRemovedContent); 86 } 87 88 // After DoTransaction() and RedoTransaction(), the left node is removed from 89 // the content tree and right node remains. 90 NS_IMETHODIMP JoinNodesTransaction::DoTransaction() { 91 MOZ_LOG(GetLogModule(), LogLevel::Info, 92 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, 93 ToString(*this).c_str())); 94 95 return DoTransactionInternal(RedoingTransaction::No); 96 } 97 98 nsresult JoinNodesTransaction::DoTransactionInternal( 99 RedoingTransaction aRedoingTransaction) { 100 if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mKeepingContent) || 101 NS_WARN_IF(!mRemovedContent) || 102 NS_WARN_IF(mRemovedContent->IsBeingRemoved()))) { 103 return NS_ERROR_NOT_AVAILABLE; 104 } 105 106 nsINode* removingContentParentNode = mRemovedContent->GetParentNode(); 107 if (MOZ_UNLIKELY(NS_WARN_IF(!removingContentParentNode))) { 108 return NS_ERROR_NOT_AVAILABLE; 109 } 110 111 // Verify that the joining content nodes have the same parent 112 if (MOZ_UNLIKELY(removingContentParentNode != 113 mKeepingContent->GetParentNode())) { 114 NS_ASSERTION(false, "Nodes do not have same parent"); 115 return NS_ERROR_NOT_AVAILABLE; 116 } 117 118 // Set this instance's mParentNode. Other methods will see a non-null 119 // mParentNode and know all is well 120 mParentNode = removingContentParentNode; 121 // For now, setting mJoinedOffset to removed content length so that 122 // CreateJoinedPoint returns a point in mKeepingContent whose offset is 123 // the result if all content in mRemovedContent are moved to start or end of 124 // mKeepingContent without any intervention. The offset will be adjusted 125 // below. 126 mJoinedOffset = mKeepingContent->Length(); 127 128 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 129 const OwningNonNull<nsIContent> removingContent = *mRemovedContent; 130 const OwningNonNull<nsIContent> keepingContent = *mKeepingContent; 131 nsresult rv; 132 // Let's try to get actual joined point with the tacker. 133 auto joinNodesPoint = EditorDOMPoint::AtEndOf(keepingContent); 134 { 135 AutoTrackDOMPoint trackJoinNodePoint(htmlEditor->RangeUpdaterRef(), 136 &joinNodesPoint); 137 rv = htmlEditor->DoJoinNodes(keepingContent, removingContent); 138 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::DoJoinNodes() failed"); 139 } 140 // Adjust join node offset to the actual offset where the original first 141 // content of the right node is. 142 mJoinedOffset = joinNodesPoint.Offset(); 143 144 if (aRedoingTransaction == RedoingTransaction::No) { 145 htmlEditor->DidJoinNodesTransaction(*this, rv); 146 } 147 148 return rv; 149 } 150 151 // XXX: What if instead of split, we just deleted the unneeded children of 152 // mRight and re-inserted mLeft? 153 NS_IMETHODIMP JoinNodesTransaction::UndoTransaction() { 154 MOZ_LOG(GetLogModule(), LogLevel::Info, 155 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, 156 ToString(*this).c_str())); 157 158 if (NS_WARN_IF(!mParentNode) || NS_WARN_IF(!mKeepingContent) || 159 NS_WARN_IF(!mRemovedContent) || NS_WARN_IF(!mHTMLEditor)) { 160 return NS_ERROR_NOT_AVAILABLE; 161 } 162 163 const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; 164 const OwningNonNull<nsIContent> removedContent = *mRemovedContent; 165 166 Result<SplitNodeResult, nsresult> splitNodeResult = htmlEditor->DoSplitNode( 167 CreateJoinedPoint<EditorDOMPoint>(), removedContent); 168 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 169 NS_WARNING("HTMLEditor::DoSplitNode() failed"); 170 return splitNodeResult.unwrapErr(); 171 } 172 // When adding caret suggestion to SplitNodeResult, here didn't change 173 // selection so that just ignore it. 174 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 175 return NS_OK; 176 } 177 178 NS_IMETHODIMP JoinNodesTransaction::RedoTransaction() { 179 MOZ_LOG(GetLogModule(), LogLevel::Info, 180 ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, 181 ToString(*this).c_str())); 182 return DoTransactionInternal(RedoingTransaction::Yes); 183 } 184 185 } // namespace mozilla