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 "CompositionTransaction.h"
7
8 #include "mozilla/EditorBase.h" // mEditorBase
9 #include "mozilla/SelectionState.h" // RangeUpdater
10 #include "mozilla/dom/Selection.h" // local var
11 #include "mozilla/dom/Text.h" // mTextNode
12 #include "nsAString.h" // params
13 #include "nsDebug.h" // for NS_ASSERTION, etc
14 #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
15 #include "nsIPresShell.h" // nsISelectionController constants
16 #include "nsRange.h" // local var
17 #include "nsQueryObject.h" // for do_QueryObject
18
19 namespace mozilla {
20
21 using namespace dom;
22
CompositionTransaction(Text & aTextNode,uint32_t aOffset,uint32_t aReplaceLength,TextRangeArray * aTextRangeArray,const nsAString & aStringToInsert,EditorBase & aEditorBase,RangeUpdater * aRangeUpdater)23 CompositionTransaction::CompositionTransaction(
24 Text& aTextNode,
25 uint32_t aOffset,
26 uint32_t aReplaceLength,
27 TextRangeArray* aTextRangeArray,
28 const nsAString& aStringToInsert,
29 EditorBase& aEditorBase,
30 RangeUpdater* aRangeUpdater)
31 : mTextNode(&aTextNode)
32 , mOffset(aOffset)
33 , mReplaceLength(aReplaceLength)
34 , mRanges(aTextRangeArray)
35 , mStringToInsert(aStringToInsert)
36 , mEditorBase(aEditorBase)
37 , mRangeUpdater(aRangeUpdater)
38 , mFixed(false)
39 {
40 MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
41 }
42
~CompositionTransaction()43 CompositionTransaction::~CompositionTransaction()
44 {
45 }
46
47 NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
48 mTextNode)
49 // mRangeList can't lead to cycles
50
51 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
52 if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
53 foundInterface = static_cast<nsITransaction*>(this);
54 } else
55 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
56
NS_IMPL_ADDREF_INHERITED(CompositionTransaction,EditTransactionBase)57 NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
58 NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
59
60 NS_IMETHODIMP
61 CompositionTransaction::DoTransaction()
62 {
63 // Fail before making any changes if there's no selection controller
64 nsCOMPtr<nsISelectionController> selCon;
65 mEditorBase.GetSelectionController(getter_AddRefs(selCon));
66 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
67
68 // Advance caret: This requires the presentation shell to get the selection.
69 if (mReplaceLength == 0) {
70 nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
71 if (NS_WARN_IF(NS_FAILED(rv))) {
72 return rv;
73 }
74 mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
75 } else {
76 uint32_t replaceableLength = mTextNode->TextLength() - mOffset;
77 nsresult rv =
78 mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
79 if (NS_WARN_IF(NS_FAILED(rv))) {
80 return rv;
81 }
82 mRangeUpdater->SelAdjDeleteText(mTextNode, mOffset, mReplaceLength);
83 mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
84
85 // If IME text node is multiple node, ReplaceData doesn't remove all IME
86 // text. So we need remove remained text into other text node.
87 if (replaceableLength < mReplaceLength) {
88 int32_t remainLength = mReplaceLength - replaceableLength;
89 nsCOMPtr<nsINode> node = mTextNode->GetNextSibling();
90 while (node && node->IsNodeOfType(nsINode::eTEXT) &&
91 remainLength > 0) {
92 Text* text = static_cast<Text*>(node.get());
93 uint32_t textLength = text->TextLength();
94 text->DeleteData(0, remainLength);
95 mRangeUpdater->SelAdjDeleteText(text, 0, remainLength);
96 remainLength -= textLength;
97 node = node->GetNextSibling();
98 }
99 }
100 }
101
102 nsresult rv = SetSelectionForRanges();
103 NS_ENSURE_SUCCESS(rv, rv);
104
105 return NS_OK;
106 }
107
108 NS_IMETHODIMP
UndoTransaction()109 CompositionTransaction::UndoTransaction()
110 {
111 // Get the selection first so we'll fail before making any changes if we
112 // can't get it
113 RefPtr<Selection> selection = mEditorBase.GetSelection();
114 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
115
116 nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
117 NS_ENSURE_SUCCESS(rv, rv);
118
119 // set the selection to the insertion point where the string was removed
120 rv = selection->Collapse(mTextNode, mOffset);
121 NS_ASSERTION(NS_SUCCEEDED(rv),
122 "Selection could not be collapsed after undo of IME insert.");
123 NS_ENSURE_SUCCESS(rv, rv);
124
125 return NS_OK;
126 }
127
128 NS_IMETHODIMP
Merge(nsITransaction * aTransaction,bool * aDidMerge)129 CompositionTransaction::Merge(nsITransaction* aTransaction,
130 bool* aDidMerge)
131 {
132 NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
133
134 // Check to make sure we aren't fixed, if we are then nothing gets absorbed
135 if (mFixed) {
136 *aDidMerge = false;
137 return NS_OK;
138 }
139
140 // If aTransaction is another CompositionTransaction then absorb it
141 RefPtr<CompositionTransaction> otherTransaction =
142 do_QueryObject(aTransaction);
143 if (otherTransaction) {
144 // We absorb the next IME transaction by adopting its insert string
145 mStringToInsert = otherTransaction->mStringToInsert;
146 mRanges = otherTransaction->mRanges;
147 *aDidMerge = true;
148 return NS_OK;
149 }
150
151 *aDidMerge = false;
152 return NS_OK;
153 }
154
155 void
MarkFixed()156 CompositionTransaction::MarkFixed()
157 {
158 mFixed = true;
159 }
160
161 NS_IMETHODIMP
GetTxnDescription(nsAString & aString)162 CompositionTransaction::GetTxnDescription(nsAString& aString)
163 {
164 aString.AssignLiteral("CompositionTransaction: ");
165 aString += mStringToInsert;
166 return NS_OK;
167 }
168
169 /* ============ private methods ================== */
170
171 nsresult
SetSelectionForRanges()172 CompositionTransaction::SetSelectionForRanges()
173 {
174 return SetIMESelection(mEditorBase, mTextNode, mOffset,
175 mStringToInsert.Length(), mRanges);
176 }
177
178 // static
179 nsresult
SetIMESelection(EditorBase & aEditorBase,Text * aTextNode,uint32_t aOffsetInNode,uint32_t aLengthOfCompositionString,const TextRangeArray * aRanges)180 CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
181 Text* aTextNode,
182 uint32_t aOffsetInNode,
183 uint32_t aLengthOfCompositionString,
184 const TextRangeArray* aRanges)
185 {
186 RefPtr<Selection> selection = aEditorBase.GetSelection();
187 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
188
189 nsresult rv = selection->StartBatchChanges();
190 NS_ENSURE_SUCCESS(rv, rv);
191
192 // First, remove all selections of IME composition.
193 static const RawSelectionType kIMESelections[] = {
194 nsISelectionController::SELECTION_IME_RAWINPUT,
195 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
196 nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
197 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
198 };
199
200 nsCOMPtr<nsISelectionController> selCon;
201 aEditorBase.GetSelectionController(getter_AddRefs(selCon));
202 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
203
204 for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
205 nsCOMPtr<nsISelection> selectionOfIME;
206 if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
207 getter_AddRefs(selectionOfIME)))) {
208 continue;
209 }
210 rv = selectionOfIME->RemoveAllRanges();
211 NS_ASSERTION(NS_SUCCEEDED(rv),
212 "Failed to remove all ranges of IME selection");
213 }
214
215 // Set caret position and selection of IME composition with TextRangeArray.
216 bool setCaret = false;
217 uint32_t countOfRanges = aRanges ? aRanges->Length() : 0;
218
219 #ifdef DEBUG
220 // Bounds-checking on debug builds
221 uint32_t maxOffset = aTextNode->Length();
222 #endif
223
224 // NOTE: composition string may be truncated when it's committed and
225 // maxlength attribute value doesn't allow input of all text of this
226 // composition.
227 for (uint32_t i = 0; i < countOfRanges; ++i) {
228 const TextRange& textRange = aRanges->ElementAt(i);
229
230 // Caret needs special handling since its length may be 0 and if it's not
231 // specified explicitly, we need to handle it ourselves later.
232 if (textRange.mRangeType == TextRangeType::eCaret) {
233 NS_ASSERTION(!setCaret, "The ranges already has caret position");
234 NS_ASSERTION(!textRange.Length(),
235 "EditorBase doesn't support wide caret");
236 int32_t caretOffset = static_cast<int32_t>(
237 aOffsetInNode +
238 std::min(textRange.mStartOffset, aLengthOfCompositionString));
239 MOZ_ASSERT(caretOffset >= 0 &&
240 static_cast<uint32_t>(caretOffset) <= maxOffset);
241 rv = selection->Collapse(aTextNode, caretOffset);
242 setCaret = setCaret || NS_SUCCEEDED(rv);
243 if (NS_WARN_IF(!setCaret)) {
244 continue;
245 }
246 // If caret range is specified explicitly, we should show the caret if
247 // it should be so.
248 aEditorBase.HideCaret(false);
249 continue;
250 }
251
252 // If the clause length is 0, it should be a bug.
253 if (!textRange.Length()) {
254 NS_WARNING("Any clauses must not be empty");
255 continue;
256 }
257
258 RefPtr<nsRange> clauseRange;
259 int32_t startOffset = static_cast<int32_t>(
260 aOffsetInNode +
261 std::min(textRange.mStartOffset, aLengthOfCompositionString));
262 MOZ_ASSERT(startOffset >= 0 &&
263 static_cast<uint32_t>(startOffset) <= maxOffset);
264 int32_t endOffset = static_cast<int32_t>(
265 aOffsetInNode +
266 std::min(textRange.mEndOffset, aLengthOfCompositionString));
267 MOZ_ASSERT(endOffset >= startOffset &&
268 static_cast<uint32_t>(endOffset) <= maxOffset);
269 rv = nsRange::CreateRange(aTextNode, startOffset,
270 aTextNode, endOffset,
271 getter_AddRefs(clauseRange));
272 if (NS_FAILED(rv)) {
273 NS_WARNING("Failed to create a DOM range for a clause of composition");
274 break;
275 }
276
277 // Set the range of the clause to selection.
278 nsCOMPtr<nsISelection> selectionOfIME;
279 rv = selCon->GetSelection(ToRawSelectionType(textRange.mRangeType),
280 getter_AddRefs(selectionOfIME));
281 if (NS_FAILED(rv)) {
282 NS_WARNING("Failed to get IME selection");
283 break;
284 }
285
286 rv = selectionOfIME->AddRange(clauseRange);
287 if (NS_FAILED(rv)) {
288 NS_WARNING("Failed to add selection range for a clause of composition");
289 break;
290 }
291
292 // Set the style of the clause.
293 nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
294 do_QueryInterface(selectionOfIME);
295 if (!selectionOfIMEPriv) {
296 NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
297 continue; // Since this is additional feature, we can continue this job.
298 }
299 rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
300 textRange.mRangeStyle);
301 if (NS_FAILED(rv)) {
302 NS_WARNING("Failed to set selection style");
303 break; // but this is unexpected...
304 }
305 }
306
307 // If the ranges doesn't include explicit caret position, let's set the
308 // caret to the end of composition string.
309 if (!setCaret) {
310 int32_t caretOffset =
311 static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
312 MOZ_ASSERT(caretOffset >= 0 &&
313 static_cast<uint32_t>(caretOffset) <= maxOffset);
314 rv = selection->Collapse(aTextNode, caretOffset);
315 NS_ASSERTION(NS_SUCCEEDED(rv),
316 "Failed to set caret at the end of composition string");
317
318 // If caret range isn't specified explicitly, we should hide the caret.
319 // Hiding the caret benefits a Windows build (see bug 555642 comment #6).
320 // However, when there is no range, we should keep showing caret.
321 if (countOfRanges) {
322 aEditorBase.HideCaret(true);
323 }
324 }
325
326 rv = selection->EndBatchChangesInternal();
327 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
328
329 return rv;
330 }
331
332 } // namespace mozilla
333