/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "JoinNodesTransaction.h" #include "EditorDOMPoint.h" // for EditorDOMPoint, etc. #include "HTMLEditHelpers.h" // for SplitNodeResult #include "JoinSplitNodeDirection.h" // JoinNodesDirection #include "HTMLEditor.h" // for HTMLEditor #include "HTMLEditorInlines.h" #include "HTMLEditUtils.h" #include "mozilla/Logging.h" #include "mozilla/ToString.h" #include "mozilla/dom/Text.h" #include "nsAString.h" #include "nsDebug.h" // for NS_ASSERTION, etc. #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. #include "nsIContent.h" // for nsIContent #include "nsISupportsImpl.h" // for QueryInterface, etc. namespace mozilla { using namespace dom; // static already_AddRefed JoinNodesTransaction::MaybeCreate( HTMLEditor& aHTMLEditor, nsIContent& aLeftContent, nsIContent& aRightContent) { RefPtr transaction = new JoinNodesTransaction(aHTMLEditor, aLeftContent, aRightContent); if (NS_WARN_IF(!transaction->CanDoIt())) { return nullptr; } return transaction.forget(); } JoinNodesTransaction::JoinNodesTransaction(HTMLEditor& aHTMLEditor, nsIContent& aLeftContent, nsIContent& aRightContent) : mHTMLEditor(&aHTMLEditor), mRemovedContent(aHTMLEditor.GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode ? &aLeftContent : &aRightContent), mKeepingContent(aHTMLEditor.GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode ? &aRightContent : &aLeftContent) { // printf("JoinNodesTransaction size: %zu\n", sizeof(JoinNodesTransaction)); static_assert(sizeof(JoinNodesTransaction) <= 64, "Transaction classes may be created a lot and may be alive " "long so that keep the foot print smaller as far as possible"); } std::ostream& operator<<(std::ostream& aStream, const JoinNodesTransaction& aTransaction) { aStream << "{ mParentNode=" << aTransaction.mParentNode.get(); if (aTransaction.mParentNode) { aStream << " (" << *aTransaction.mParentNode << ")"; } aStream << ", mRemovedContent=" << aTransaction.mRemovedContent.get(); if (aTransaction.mRemovedContent) { aStream << " (" << *aTransaction.mRemovedContent << ")"; } aStream << ", mKeepingContent=" << aTransaction.mKeepingContent.get(); if (aTransaction.mKeepingContent) { aStream << " (" << *aTransaction.mKeepingContent << ")"; } aStream << ", mJoinedOffset=" << aTransaction.mJoinedOffset << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << ", GetJoinNodesDirection()=" << aTransaction.GetJoinNodesDirection() << " }"; return aStream; } NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodesTransaction, EditTransactionBase, mHTMLEditor, mParentNode, mRemovedContent, mKeepingContent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodesTransaction) NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) SplitNodeDirection JoinNodesTransaction::GetSplitNodeDirection() const { return MOZ_LIKELY(mHTMLEditor) ? mHTMLEditor->GetSplitNodeDirection() : SplitNodeDirection::LeftNodeIsNewOne; } JoinNodesDirection JoinNodesTransaction::GetJoinNodesDirection() const { return MOZ_LIKELY(mHTMLEditor) ? mHTMLEditor->GetJoinNodesDirection() : JoinNodesDirection::LeftNodeIntoRightNode; } bool JoinNodesTransaction::CanDoIt() const { if (NS_WARN_IF(!mKeepingContent) || NS_WARN_IF(!mRemovedContent) || NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(mRemovedContent->IsBeingRemoved()) || !mKeepingContent->IsInComposedDoc()) { return false; } return HTMLEditUtils::IsRemovableFromParentNode(*mRemovedContent); } // After DoTransaction() and RedoTransaction(), the left node is removed from // the content tree and right node remains. NS_IMETHODIMP JoinNodesTransaction::DoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, ToString(*this).c_str())); return DoTransactionInternal(RedoingTransaction::No); } nsresult JoinNodesTransaction::DoTransactionInternal( RedoingTransaction aRedoingTransaction) { if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mKeepingContent) || NS_WARN_IF(!mRemovedContent) || NS_WARN_IF(mRemovedContent->IsBeingRemoved()))) { return NS_ERROR_NOT_AVAILABLE; } nsINode* removingContentParentNode = mRemovedContent->GetParentNode(); if (MOZ_UNLIKELY(NS_WARN_IF(!removingContentParentNode))) { return NS_ERROR_NOT_AVAILABLE; } // Verify that the joining content nodes have the same parent if (MOZ_UNLIKELY(removingContentParentNode != mKeepingContent->GetParentNode())) { NS_ASSERTION(false, "Nodes do not have same parent"); return NS_ERROR_NOT_AVAILABLE; } // Set this instance's mParentNode. Other methods will see a non-null // mParentNode and know all is well mParentNode = removingContentParentNode; // For now, setting mJoinedOffset to removed content length so that // CreateJoinedPoint returns a point in mKeepingContent whose offset is // the result if all content in mRemovedContent are moved to start or end of // mKeepingContent without any intervation. The offset will be adjusted // below. mJoinedOffset = GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode ? mRemovedContent->Length() : mKeepingContent->Length(); const OwningNonNull htmlEditor = *mHTMLEditor; const OwningNonNull removingContent = *mRemovedContent; const OwningNonNull keepingContent = *mKeepingContent; nsresult rv; // Let's try to get actual joined point with the tacker. EditorDOMPoint joinNodesPoint = GetJoinNodesDirection() == JoinNodesDirection::LeftNodeIntoRightNode ? EditorDOMPoint(keepingContent, 0u) : EditorDOMPoint::AtEndOf(keepingContent); { AutoTrackDOMPoint trackJoinNodePoint(htmlEditor->RangeUpdaterRef(), &joinNodesPoint); rv = htmlEditor->DoJoinNodes(keepingContent, removingContent, GetJoinNodesDirection()); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::DoJoinNodes() failed"); } // Adjust join node offset to the actual offset where the original first // content of the right node is. mJoinedOffset = joinNodesPoint.Offset(); if (aRedoingTransaction == RedoingTransaction::No) { htmlEditor->DidJoinNodesTransaction(*this, rv); } return rv; } // XXX: What if instead of split, we just deleted the unneeded children of // mRight and re-inserted mLeft? NS_IMETHODIMP JoinNodesTransaction::UndoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, ToString(*this).c_str())); if (NS_WARN_IF(!mParentNode) || NS_WARN_IF(!mKeepingContent) || NS_WARN_IF(!mRemovedContent) || NS_WARN_IF(!mHTMLEditor)) { return NS_ERROR_NOT_AVAILABLE; } const OwningNonNull htmlEditor = *mHTMLEditor; const OwningNonNull removedContent = *mRemovedContent; Result splitNodeResult = htmlEditor->DoSplitNode(CreateJoinedPoint(), removedContent, GetSplitNodeDirection()); if (MOZ_UNLIKELY(splitNodeResult.isErr())) { NS_WARNING("HTMLEditor::DoSplitNode() failed"); return splitNodeResult.unwrapErr(); } // When adding caret suggestion to SplitNodeResult, here didn't change // selection so that just ignore it. splitNodeResult.inspect().IgnoreCaretPointSuggestion(); return NS_OK; } NS_IMETHODIMP JoinNodesTransaction::RedoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p JoinNodesTransaction::%s this=%s", this, __FUNCTION__, ToString(*this).c_str())); return DoTransactionInternal(RedoingTransaction::Yes); } } // namespace mozilla