Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/DeleteRangeTransaction.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 "DeleteRangeTransaction.h"
7
8
#include "DeleteNodeTransaction.h"
9
#include "DeleteTextTransaction.h"
10
#include "mozilla/Assertions.h"
11
#include "mozilla/EditorBase.h"
12
#include "mozilla/dom/Selection.h"
13
#include "mozilla/mozalloc.h"
14
#include "mozilla/RangeBoundary.h"
15
#include "nsCOMPtr.h"
16
#include "nsDebug.h"
17
#include "nsError.h"
18
#include "nsIContent.h"
19
#include "nsIContentIterator.h"
20
#include "nsINode.h"
21
#include "nsAString.h"
22
23
namespace mozilla {
24
25
using namespace dom;
26
27
DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
28
                                               nsRange& aRangeToDelete)
29
  : mEditorBase(&aEditorBase)
30
  , mRangeToDelete(aRangeToDelete.CloneRange())
31
0
{
32
0
}
33
34
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
35
                                   EditAggregateTransaction,
36
                                   mEditorBase,
37
                                   mRangeToDelete)
38
39
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
40
0
NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
41
42
NS_IMETHODIMP
43
DeleteRangeTransaction::DoTransaction()
44
0
{
45
0
  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mRangeToDelete)) {
46
0
    return NS_ERROR_NOT_AVAILABLE;
47
0
  }
48
0
49
0
  // Swap mRangeToDelete out into a stack variable, so we make sure to null it
50
0
  // out on return from this function.  Once this function returns, we no longer
51
0
  // need mRangeToDelete, and keeping it alive in the long term slows down all
52
0
  // DOM mutations because it's observing them.
53
0
  RefPtr<nsRange> rangeToDelete;
54
0
  rangeToDelete.swap(mRangeToDelete);
55
0
56
0
  // build the child transactions
57
0
  const RangeBoundary& startRef = rangeToDelete->StartRef();
58
0
  const RangeBoundary& endRef = rangeToDelete->EndRef();
59
0
  MOZ_ASSERT(startRef.IsSetAndValid());
60
0
  MOZ_ASSERT(endRef.IsSetAndValid());
61
0
62
0
  if (startRef.Container() == endRef.Container()) {
63
0
    // the selection begins and ends in the same node
64
0
    nsresult rv = CreateTxnsToDeleteBetween(startRef.AsRaw(), endRef.AsRaw());
65
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
66
0
      return rv;
67
0
    }
68
0
  } else {
69
0
    // the selection ends in a different node from where it started.  delete
70
0
    // the relevant content in the start node
71
0
    nsresult rv = CreateTxnsToDeleteContent(startRef.AsRaw(), nsIEditor::eNext);
72
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
73
0
      return rv;
74
0
    }
75
0
    // delete the intervening nodes
76
0
    rv = CreateTxnsToDeleteNodesBetween(rangeToDelete);
77
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
78
0
      return rv;
79
0
    }
80
0
    // delete the relevant content in the end node
81
0
    rv = CreateTxnsToDeleteContent(endRef.AsRaw(), nsIEditor::ePrevious);
82
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
83
0
      return rv;
84
0
    }
85
0
  }
86
0
87
0
  // if we've successfully built this aggregate transaction, then do it.
88
0
  nsresult rv = EditAggregateTransaction::DoTransaction();
89
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
90
0
    return rv;
91
0
  }
92
0
93
0
  if (!mEditorBase->AllowsTransactionsToChangeSelection()) {
94
0
    return NS_OK;
95
0
  }
96
0
97
0
  RefPtr<Selection> selection = mEditorBase->GetSelection();
98
0
  if (NS_WARN_IF(!selection)) {
99
0
    return NS_ERROR_NULL_POINTER;
100
0
  }
101
0
  rv = selection->Collapse(startRef.AsRaw());
102
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
103
0
    return rv;
104
0
  }
105
0
106
0
  return NS_OK;
107
0
}
108
109
NS_IMETHODIMP
110
DeleteRangeTransaction::UndoTransaction()
111
0
{
112
0
  return EditAggregateTransaction::UndoTransaction();
113
0
}
114
115
NS_IMETHODIMP
116
DeleteRangeTransaction::RedoTransaction()
117
0
{
118
0
  return EditAggregateTransaction::RedoTransaction();
119
0
}
120
121
nsresult
122
DeleteRangeTransaction::CreateTxnsToDeleteBetween(
123
                          const RawRangeBoundary& aStart,
124
                          const RawRangeBoundary& aEnd)
125
0
{
126
0
  if (NS_WARN_IF(!aStart.IsSetAndValid()) ||
127
0
      NS_WARN_IF(!aEnd.IsSetAndValid()) ||
128
0
      NS_WARN_IF(aStart.Container() != aEnd.Container())) {
129
0
    return NS_ERROR_INVALID_ARG;
130
0
  }
131
0
132
0
  if (NS_WARN_IF(!mEditorBase)) {
133
0
    return NS_ERROR_NOT_AVAILABLE;
134
0
  }
135
0
136
0
  // see what kind of node we have
137
0
  if (RefPtr<CharacterData> charDataNode =
138
0
        CharacterData::FromNode(aStart.Container())) {
139
0
    // if the node is a chardata node, then delete chardata content
140
0
    int32_t numToDel;
141
0
    if (aStart == aEnd) {
142
0
      numToDel = 1;
143
0
    } else {
144
0
      numToDel = aEnd.Offset() - aStart.Offset();
145
0
      MOZ_DIAGNOSTIC_ASSERT(numToDel > 0);
146
0
    }
147
0
148
0
    RefPtr<DeleteTextTransaction> deleteTextTransaction =
149
0
      DeleteTextTransaction::MaybeCreate(*mEditorBase, *charDataNode,
150
0
                                         aStart.Offset(), numToDel);
151
0
    // If the text node isn't editable, it should be never undone/redone.
152
0
    // So, the transaction shouldn't be recorded.
153
0
    if (NS_WARN_IF(!deleteTextTransaction)) {
154
0
      return NS_ERROR_FAILURE;
155
0
    }
156
0
    AppendChild(deleteTextTransaction);
157
0
    return NS_OK;
158
0
  }
159
0
160
0
  // Even if we detect invalid range, we should ignore it for removing
161
0
  // specified range's nodes as far as possible.
162
0
  for (nsIContent* child = aStart.GetChildAtOffset();
163
0
       child && child != aEnd.GetChildAtOffset();
164
0
       child = child->GetNextSibling()) {
165
0
    RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
166
0
      DeleteNodeTransaction::MaybeCreate(*mEditorBase, *child);
167
0
    // XXX This is odd handling.  Even if some children are not editable,
168
0
    //     editor should append transactions because they could be editable
169
0
    //     at undoing/redoing.  Additionally, if the transaction needs to
170
0
    //     delete/restore all nodes, it should at undoing/redoing.
171
0
    if (deleteNodeTransaction) {
172
0
      AppendChild(deleteNodeTransaction);
173
0
    }
174
0
  }
175
0
176
0
  return NS_OK;
177
0
}
178
179
nsresult
180
DeleteRangeTransaction::CreateTxnsToDeleteContent(
181
                          const RawRangeBoundary& aPoint,
182
                          nsIEditor::EDirection aAction)
183
0
{
184
0
  if (NS_WARN_IF(!aPoint.IsSetAndValid())) {
185
0
    return NS_ERROR_INVALID_ARG;
186
0
  }
187
0
188
0
  if (NS_WARN_IF(!mEditorBase)) {
189
0
    return NS_ERROR_NOT_AVAILABLE;
190
0
  }
191
0
192
0
  RefPtr<CharacterData> dataNode = CharacterData::FromNode(aPoint.Container());
193
0
  if (!dataNode) {
194
0
    return NS_OK;
195
0
  }
196
0
197
0
  // If the node is a chardata node, then delete chardata content
198
0
  uint32_t startOffset, numToDelete;
199
0
  if (nsIEditor::eNext == aAction) {
200
0
    startOffset = aPoint.Offset();
201
0
    numToDelete = aPoint.Container()->Length() - aPoint.Offset();
202
0
  } else {
203
0
    startOffset = 0;
204
0
    numToDelete = aPoint.Offset();
205
0
  }
206
0
207
0
  if (!numToDelete) {
208
0
    return NS_OK;
209
0
  }
210
0
211
0
  RefPtr<DeleteTextTransaction> deleteTextTransaction =
212
0
    DeleteTextTransaction::MaybeCreate(*mEditorBase, *dataNode,
213
0
                                       startOffset, numToDelete);
214
0
  // If the text node isn't editable, it should be never undone/redone.
215
0
  // So, the transaction shouldn't be recorded.
216
0
  if (NS_WARN_IF(!deleteTextTransaction)) {
217
0
    return NS_ERROR_FAILURE;
218
0
  }
219
0
  AppendChild(deleteTextTransaction);
220
0
221
0
  return NS_OK;
222
0
}
223
224
nsresult
225
DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween(nsRange* aRangeToDelete)
226
0
{
227
0
  if (NS_WARN_IF(!mEditorBase)) {
228
0
    return NS_ERROR_NOT_AVAILABLE;
229
0
  }
230
0
231
0
  nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
232
0
233
0
  nsresult rv = iter->Init(aRangeToDelete);
234
0
  NS_ENSURE_SUCCESS(rv, rv);
235
0
236
0
  while (!iter->IsDone()) {
237
0
    nsCOMPtr<nsINode> node = iter->GetCurrentNode();
238
0
    if (NS_WARN_IF(!node)) {
239
0
      return NS_ERROR_NULL_POINTER;
240
0
    }
241
0
242
0
    RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
243
0
      DeleteNodeTransaction::MaybeCreate(*mEditorBase, *node);
244
0
    // XXX This is odd handling.  Even if some nodes in the range are not
245
0
    //     editable, editor should append transactions because they could
246
0
    //     at undoing/redoing.  Additionally, if the transaction needs to
247
0
    //     delete/restore all nodes, it should at undoing/redoing.
248
0
    if (NS_WARN_IF(!deleteNodeTransaction)) {
249
0
      return NS_ERROR_FAILURE;
250
0
    }
251
0
    AppendChild(deleteNodeTransaction);
252
0
253
0
    iter->Next();
254
0
  }
255
0
  return NS_OK;
256
0
}
257
258
} // namespace mozilla