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