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