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 "InsertNodeTransaction.h"
7
8 #include "mozilla/EditorBase.h" // for EditorBase
9 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
10 #include "mozilla/HTMLEditor.h" // for HTMLEditor
11 #include "mozilla/Logging.h"
12 #include "mozilla/TextEditor.h" // for TextEditor
13 #include "mozilla/ToString.h"
14
15 #include "mozilla/dom/Selection.h" // for Selection
16
17 #include "nsAString.h"
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 "nsMemory.h" // for nsMemory
22 #include "nsReadableUtils.h" // for ToNewCString
23 #include "nsString.h" // for nsString
24
25 namespace mozilla {
26
27 using namespace dom;
28
29 template already_AddRefed<InsertNodeTransaction> InsertNodeTransaction::Create(
30 EditorBase& aEditorBase, nsIContent& aContentToInsert,
31 const EditorDOMPoint& aPointToInsert);
32 template already_AddRefed<InsertNodeTransaction> InsertNodeTransaction::Create(
33 EditorBase& aEditorBase, nsIContent& aContentToInsert,
34 const EditorRawDOMPoint& aPointToInsert);
35
36 // static
37 template <typename PT, typename CT>
Create(EditorBase & aEditorBase,nsIContent & aContentToInsert,const EditorDOMPointBase<PT,CT> & aPointToInsert)38 already_AddRefed<InsertNodeTransaction> InsertNodeTransaction::Create(
39 EditorBase& aEditorBase, nsIContent& aContentToInsert,
40 const EditorDOMPointBase<PT, CT>& aPointToInsert) {
41 RefPtr<InsertNodeTransaction> transaction =
42 new InsertNodeTransaction(aEditorBase, aContentToInsert, aPointToInsert);
43 return transaction.forget();
44 }
45
46 template <typename PT, typename CT>
InsertNodeTransaction(EditorBase & aEditorBase,nsIContent & aContentToInsert,const EditorDOMPointBase<PT,CT> & aPointToInsert)47 InsertNodeTransaction::InsertNodeTransaction(
48 EditorBase& aEditorBase, nsIContent& aContentToInsert,
49 const EditorDOMPointBase<PT, CT>& aPointToInsert)
50 : mContentToInsert(&aContentToInsert),
51 mPointToInsert(aPointToInsert),
52 mEditorBase(&aEditorBase) {
53 MOZ_ASSERT(mPointToInsert.IsSetAndValid());
54 // Ensure mPointToInsert stores child at offset.
55 Unused << mPointToInsert.GetChild();
56 }
57
operator <<(std::ostream & aStream,const InsertNodeTransaction & aTransaction)58 std::ostream& operator<<(std::ostream& aStream,
59 const InsertNodeTransaction& aTransaction) {
60 aStream << "{ mContentToInsert=" << aTransaction.mContentToInsert.get();
61 if (aTransaction.mContentToInsert) {
62 if (aTransaction.mContentToInsert->IsText()) {
63 nsAutoString data;
64 aTransaction.mContentToInsert->AsText()->GetData(data);
65 aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")";
66 } else {
67 aStream << " (" << *aTransaction.mContentToInsert << ")";
68 }
69 }
70 aStream << ", mPointToInsert=" << aTransaction.mPointToInsert
71 << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
72 return aStream;
73 }
74
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction,EditTransactionBase,mEditorBase,mContentToInsert,mPointToInsert)75 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
76 mEditorBase, mContentToInsert,
77 mPointToInsert)
78
79 NS_IMPL_ADDREF_INHERITED(InsertNodeTransaction, EditTransactionBase)
80 NS_IMPL_RELEASE_INHERITED(InsertNodeTransaction, EditTransactionBase)
81 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTransaction)
82 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
83
84 NS_IMETHODIMP InsertNodeTransaction::DoTransaction() {
85 MOZ_LOG(GetLogModule(), LogLevel::Info,
86 ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
87 ToString(*this).c_str()));
88
89 if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mContentToInsert) ||
90 NS_WARN_IF(!mPointToInsert.IsSet())) {
91 return NS_ERROR_NOT_AVAILABLE;
92 }
93
94 MOZ_ASSERT_IF(mEditorBase->IsTextEditor(), !mContentToInsert->IsText());
95
96 if (!mPointToInsert.IsSetAndValid()) {
97 // It seems that DOM tree has been changed after first DoTransaction()
98 // and current RedoTranaction() call.
99 if (mPointToInsert.GetChild()) {
100 EditorDOMPoint newPointToInsert(mPointToInsert.GetChild());
101 if (!newPointToInsert.IsSet()) {
102 // The insertion point has been removed from the DOM tree.
103 // In this case, we should append the node to the container instead.
104 newPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
105 if (NS_WARN_IF(!newPointToInsert.IsSet())) {
106 return NS_ERROR_FAILURE;
107 }
108 }
109 mPointToInsert = newPointToInsert;
110 } else {
111 mPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
112 if (NS_WARN_IF(!mPointToInsert.IsSet())) {
113 return NS_ERROR_FAILURE;
114 }
115 }
116 }
117
118 OwningNonNull<EditorBase> editorBase = *mEditorBase;
119 OwningNonNull<nsIContent> contentToInsert = *mContentToInsert;
120 OwningNonNull<nsINode> container = *mPointToInsert.GetContainer();
121 nsCOMPtr<nsIContent> refChild = mPointToInsert.GetChild();
122 if (contentToInsert->IsElement()) {
123 nsresult rv = editorBase->MarkElementDirty(
124 MOZ_KnownLive(*contentToInsert->AsElement()));
125 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
126 return EditorBase::ToGenericNSResult(rv);
127 }
128 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
129 "EditorBase::MarkElementDirty() failed, but ignored");
130 }
131
132 ErrorResult error;
133 container->InsertBefore(contentToInsert, refChild, error);
134 // InsertBefore() may call MightThrowJSException() even if there is no
135 // error. We don't need the flag here.
136 error.WouldReportJSException();
137 if (error.Failed()) {
138 NS_WARNING("nsINode::InsertBefore() failed");
139 return error.StealNSResult();
140 }
141
142 if (!mEditorBase->AllowsTransactionsToChangeSelection()) {
143 return NS_OK;
144 }
145
146 RefPtr<Selection> selection = mEditorBase->GetSelection();
147 if (NS_WARN_IF(!selection)) {
148 return NS_ERROR_FAILURE;
149 }
150
151 // Place the selection just after the inserted element.
152 EditorRawDOMPoint afterInsertedNode(
153 EditorRawDOMPoint::After(contentToInsert));
154 NS_WARNING_ASSERTION(afterInsertedNode.IsSet(),
155 "Failed to set after the inserted node");
156 IgnoredErrorResult ignoredError;
157 selection->CollapseInLimiter(afterInsertedNode, ignoredError);
158 NS_WARNING_ASSERTION(!ignoredError.Failed(),
159 "Selection::CollapseInLimiter() failed, but ignored");
160 return NS_OK;
161 }
162
UndoTransaction()163 NS_IMETHODIMP InsertNodeTransaction::UndoTransaction() {
164 MOZ_LOG(GetLogModule(), LogLevel::Info,
165 ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
166 ToString(*this).c_str()));
167
168 if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mContentToInsert) ||
169 NS_WARN_IF(!mPointToInsert.IsSet())) {
170 return NS_ERROR_NOT_INITIALIZED;
171 }
172 // XXX If the inserted node has been moved to different container node or
173 // just removed from the DOM tree, this always fails.
174 OwningNonNull<nsINode> container = *mPointToInsert.GetContainer();
175 OwningNonNull<nsIContent> contentToInsert = *mContentToInsert;
176 ErrorResult error;
177 container->RemoveChild(contentToInsert, error);
178 NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
179 return error.StealNSResult();
180 }
181
RedoTransaction()182 NS_IMETHODIMP InsertNodeTransaction::RedoTransaction() {
183 MOZ_LOG(GetLogModule(), LogLevel::Info,
184 ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
185 ToString(*this).c_str()));
186 return DoTransaction();
187 }
188
189 } // namespace mozilla
190