Coverage Report

Created: 2018-09-25 14:53

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