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 "InsertTextTransaction.h"
7 
8 #include "mozilla/EditorBase.h"  // mEditorBase
9 #include "mozilla/Logging.h"
10 #include "mozilla/SelectionState.h"  // RangeUpdater
11 #include "mozilla/ToString.h"
12 #include "mozilla/dom/Selection.h"  // Selection local var
13 #include "mozilla/dom/Text.h"       // mTextNode
14 #include "nsAString.h"              // nsAString parameter
15 #include "nsDebug.h"                // for NS_ASSERTION, etc.
16 #include "nsError.h"                // for NS_OK, etc.
17 #include "nsQueryObject.h"          // for do_QueryObject
18 
19 namespace mozilla {
20 
21 using namespace dom;
22 
23 // static
Create(EditorBase & aEditorBase,const nsAString & aStringToInsert,const EditorDOMPointInText & aPointToInsert)24 already_AddRefed<InsertTextTransaction> InsertTextTransaction::Create(
25     EditorBase& aEditorBase, const nsAString& aStringToInsert,
26     const EditorDOMPointInText& aPointToInsert) {
27   MOZ_ASSERT(aPointToInsert.IsSetAndValid());
28   RefPtr<InsertTextTransaction> transaction =
29       new InsertTextTransaction(aEditorBase, aStringToInsert, aPointToInsert);
30   return transaction.forget();
31 }
32 
InsertTextTransaction(EditorBase & aEditorBase,const nsAString & aStringToInsert,const EditorDOMPointInText & aPointToInsert)33 InsertTextTransaction::InsertTextTransaction(
34     EditorBase& aEditorBase, const nsAString& aStringToInsert,
35     const EditorDOMPointInText& aPointToInsert)
36     : mTextNode(aPointToInsert.ContainerAsText()),
37       mOffset(aPointToInsert.Offset()),
38       mStringToInsert(aStringToInsert),
39       mEditorBase(&aEditorBase) {}
40 
operator <<(std::ostream & aStream,const InsertTextTransaction & aTransaction)41 std::ostream& operator<<(std::ostream& aStream,
42                          const InsertTextTransaction& aTransaction) {
43   aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
44   if (aTransaction.mTextNode) {
45     aStream << " (" << *aTransaction.mTextNode << ")";
46   }
47   aStream << ", mOffset=" << aTransaction.mOffset << ", mStringToInsert=\""
48           << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
49           << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
50   return aStream;
51 }
52 
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction,EditTransactionBase,mEditorBase,mTextNode)53 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase,
54                                    mEditorBase, mTextNode)
55 
56 NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase)
57 NS_IMPL_RELEASE_INHERITED(InsertTextTransaction, EditTransactionBase)
58 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTransaction)
59 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
60 
61 NS_IMETHODIMP InsertTextTransaction::DoTransaction() {
62   MOZ_LOG(GetLogModule(), LogLevel::Info,
63           ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
64            ToString(*this).c_str()));
65 
66   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
67     return NS_ERROR_NOT_AVAILABLE;
68   }
69 
70   OwningNonNull<EditorBase> editorBase = *mEditorBase;
71   OwningNonNull<Text> textNode = *mTextNode;
72 
73   ErrorResult error;
74   editorBase->DoInsertText(textNode, mOffset, mStringToInsert, error);
75   if (error.Failed()) {
76     NS_WARNING("EditorBase::DoInsertText() failed");
77     return error.StealNSResult();
78   }
79 
80   // Only set selection to insertion point if editor gives permission
81   if (editorBase->AllowsTransactionsToChangeSelection()) {
82     RefPtr<Selection> selection = editorBase->GetSelection();
83     if (NS_WARN_IF(!selection)) {
84       return NS_ERROR_FAILURE;
85     }
86     DebugOnly<nsresult> rvIgnored = selection->CollapseInLimiter(
87         textNode, mOffset + mStringToInsert.Length());
88     NS_ASSERTION(NS_SUCCEEDED(rvIgnored),
89                  "Selection::CollapseInLimiter() failed, but ignored");
90   } else {
91     // Do nothing - DOM Range gravity will adjust selection
92   }
93   // XXX Other transactions do not do this but its callers do.
94   //     Why do this transaction do this by itself?
95   editorBase->RangeUpdaterRef().SelAdjInsertText(textNode, mOffset,
96                                                  mStringToInsert.Length());
97 
98   return NS_OK;
99 }
100 
UndoTransaction()101 NS_IMETHODIMP InsertTextTransaction::UndoTransaction() {
102   MOZ_LOG(GetLogModule(), LogLevel::Info,
103           ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
104            ToString(*this).c_str()));
105 
106   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
107     return NS_ERROR_NOT_INITIALIZED;
108   }
109   OwningNonNull<EditorBase> editorBase = *mEditorBase;
110   OwningNonNull<Text> textNode = *mTextNode;
111   ErrorResult error;
112   editorBase->DoDeleteText(textNode, mOffset, mStringToInsert.Length(), error);
113   NS_WARNING_ASSERTION(!error.Failed(), "EditorBase::DoDeleteText() failed");
114   return error.StealNSResult();
115 }
116 
RedoTransaction()117 NS_IMETHODIMP InsertTextTransaction::RedoTransaction() {
118   MOZ_LOG(GetLogModule(), LogLevel::Info,
119           ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
120            ToString(*this).c_str()));
121   return DoTransaction();
122 }
123 
Merge(nsITransaction * aOtherTransaction,bool * aDidMerge)124 NS_IMETHODIMP InsertTextTransaction::Merge(nsITransaction* aOtherTransaction,
125                                            bool* aDidMerge) {
126   MOZ_LOG(GetLogModule(), LogLevel::Debug,
127           ("%p InsertTextTransaction::%s(aOtherTransaction=%p) this=%s", this,
128            __FUNCTION__, aOtherTransaction, ToString(*this).c_str()));
129 
130   if (NS_WARN_IF(!aOtherTransaction) || NS_WARN_IF(!aDidMerge)) {
131     return NS_ERROR_INVALID_ARG;
132   }
133   // Set out param default value
134   *aDidMerge = false;
135 
136   RefPtr<EditTransactionBase> otherTransactionBase =
137       aOtherTransaction->GetAsEditTransactionBase();
138   if (!otherTransactionBase) {
139     MOZ_LOG(
140         GetLogModule(), LogLevel::Debug,
141         ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false",
142          this, __FUNCTION__, aOtherTransaction));
143     return NS_OK;
144   }
145 
146   // If aTransaction is a InsertTextTransaction, and if the selection hasn't
147   // changed, then absorb it.
148   InsertTextTransaction* otherInsertTextTransaction =
149       otherTransactionBase->GetAsInsertTextTransaction();
150   if (!otherInsertTextTransaction ||
151       !IsSequentialInsert(*otherInsertTextTransaction)) {
152     MOZ_LOG(
153         GetLogModule(), LogLevel::Debug,
154         ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false",
155          this, __FUNCTION__, aOtherTransaction));
156     return NS_OK;
157   }
158 
159   nsAutoString otherData;
160   otherInsertTextTransaction->GetData(otherData);
161   mStringToInsert += otherData;
162   *aDidMerge = true;
163   MOZ_LOG(GetLogModule(), LogLevel::Debug,
164           ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned true",
165            this, __FUNCTION__, aOtherTransaction));
166   return NS_OK;
167 }
168 
169 /* ============ private methods ================== */
170 
GetData(nsString & aResult)171 void InsertTextTransaction::GetData(nsString& aResult) {
172   aResult = mStringToInsert;
173 }
174 
IsSequentialInsert(InsertTextTransaction & aOtherTransaction)175 bool InsertTextTransaction::IsSequentialInsert(
176     InsertTextTransaction& aOtherTransaction) {
177   return aOtherTransaction.mTextNode == mTextNode &&
178          aOtherTransaction.mOffset == mOffset + mStringToInsert.Length();
179 }
180 
181 }  // namespace mozilla
182