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 "nsCOMPtr.h"
15 #include "nsDebug.h"
16 #include "nsError.h"
17 #include "nsIContent.h"
18 #include "nsIContentIterator.h"
19 #include "nsIDOMCharacterData.h"
20 #include "nsINode.h"
21 #include "nsAString.h"
22
23 namespace mozilla {
24
25 using namespace dom;
26
27 // note that aEditorBase is not refcounted
DeleteRangeTransaction()28 DeleteRangeTransaction::DeleteRangeTransaction()
29 : mEditorBase(nullptr)
30 , mRangeUpdater(nullptr)
31 {
32 }
33
NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,EditAggregateTransaction,mRange)34 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
35 EditAggregateTransaction,
36 mRange)
37
38 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
39 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
40
41 nsresult
42 DeleteRangeTransaction::Init(EditorBase* aEditorBase,
43 nsRange* aRange,
44 RangeUpdater* aRangeUpdater)
45 {
46 MOZ_ASSERT(aEditorBase && aRange);
47
48 mEditorBase = aEditorBase;
49 mRange = aRange->CloneRange();
50 mRangeUpdater = aRangeUpdater;
51
52 NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetStartParent()),
53 NS_ERROR_FAILURE);
54 NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetEndParent()),
55 NS_ERROR_FAILURE);
56 NS_ENSURE_TRUE(mEditorBase->IsModifiableNode(mRange->GetCommonAncestor()),
57 NS_ERROR_FAILURE);
58
59 return NS_OK;
60 }
61
62 NS_IMETHODIMP
DoTransaction()63 DeleteRangeTransaction::DoTransaction()
64 {
65 MOZ_ASSERT(mRange && mEditorBase);
66
67 // build the child transactions
68 nsCOMPtr<nsINode> startParent = mRange->GetStartParent();
69 int32_t startOffset = mRange->StartOffset();
70 nsCOMPtr<nsINode> endParent = mRange->GetEndParent();
71 int32_t endOffset = mRange->EndOffset();
72 MOZ_ASSERT(startParent && endParent);
73
74 if (startParent == endParent) {
75 // the selection begins and ends in the same node
76 nsresult rv =
77 CreateTxnsToDeleteBetween(startParent, startOffset, endOffset);
78 NS_ENSURE_SUCCESS(rv, rv);
79 } else {
80 // the selection ends in a different node from where it started. delete
81 // the relevant content in the start node
82 nsresult rv =
83 CreateTxnsToDeleteContent(startParent, startOffset, nsIEditor::eNext);
84 NS_ENSURE_SUCCESS(rv, rv);
85 // delete the intervening nodes
86 rv = CreateTxnsToDeleteNodesBetween();
87 NS_ENSURE_SUCCESS(rv, rv);
88 // delete the relevant content in the end node
89 rv = CreateTxnsToDeleteContent(endParent, endOffset, nsIEditor::ePrevious);
90 NS_ENSURE_SUCCESS(rv, rv);
91 }
92
93 // if we've successfully built this aggregate transaction, then do it.
94 nsresult rv = EditAggregateTransaction::DoTransaction();
95 NS_ENSURE_SUCCESS(rv, rv);
96
97 // only set selection to deletion point if editor gives permission
98 bool bAdjustSelection;
99 mEditorBase->ShouldTxnSetSelection(&bAdjustSelection);
100 if (bAdjustSelection) {
101 RefPtr<Selection> selection = mEditorBase->GetSelection();
102 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
103 rv = selection->Collapse(startParent, startOffset);
104 NS_ENSURE_SUCCESS(rv, rv);
105 }
106 // else do nothing - dom range gravity will adjust selection
107
108 return NS_OK;
109 }
110
111 NS_IMETHODIMP
UndoTransaction()112 DeleteRangeTransaction::UndoTransaction()
113 {
114 MOZ_ASSERT(mRange && mEditorBase);
115
116 return EditAggregateTransaction::UndoTransaction();
117 }
118
119 NS_IMETHODIMP
RedoTransaction()120 DeleteRangeTransaction::RedoTransaction()
121 {
122 MOZ_ASSERT(mRange && mEditorBase);
123
124 return EditAggregateTransaction::RedoTransaction();
125 }
126
127 NS_IMETHODIMP
GetTxnDescription(nsAString & aString)128 DeleteRangeTransaction::GetTxnDescription(nsAString& aString)
129 {
130 aString.AssignLiteral("DeleteRangeTransaction");
131 return NS_OK;
132 }
133
134 nsresult
CreateTxnsToDeleteBetween(nsINode * aNode,int32_t aStartOffset,int32_t aEndOffset)135 DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
136 int32_t aStartOffset,
137 int32_t aEndOffset)
138 {
139 // see what kind of node we have
140 if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
141 // if the node is a chardata node, then delete chardata content
142 int32_t numToDel;
143 if (aStartOffset == aEndOffset) {
144 numToDel = 1;
145 } else {
146 numToDel = aEndOffset - aStartOffset;
147 }
148
149 RefPtr<nsGenericDOMDataNode> charDataNode =
150 static_cast<nsGenericDOMDataNode*>(aNode);
151
152 RefPtr<DeleteTextTransaction> transaction =
153 new DeleteTextTransaction(*mEditorBase, *charDataNode, aStartOffset,
154 numToDel, mRangeUpdater);
155
156 nsresult rv = transaction->Init();
157 NS_ENSURE_SUCCESS(rv, rv);
158
159 AppendChild(transaction);
160 return NS_OK;
161 }
162
163 nsCOMPtr<nsIContent> child = aNode->GetChildAt(aStartOffset);
164 NS_ENSURE_STATE(child);
165
166 // XXX This looks odd. Only when the last transaction causes error at
167 // calling Init(), the result becomes error. Otherwise, always NS_OK.
168 nsresult rv = NS_OK;
169 for (int32_t i = aStartOffset; i < aEndOffset; ++i) {
170 RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
171 rv = transaction->Init(mEditorBase, child, mRangeUpdater);
172 if (NS_SUCCEEDED(rv)) {
173 AppendChild(transaction);
174 }
175
176 child = child->GetNextSibling();
177 }
178
179 NS_ENSURE_SUCCESS(rv, rv);
180 return NS_OK;
181 }
182
183 nsresult
CreateTxnsToDeleteContent(nsINode * aNode,int32_t aOffset,nsIEditor::EDirection aAction)184 DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
185 int32_t aOffset,
186 nsIEditor::EDirection aAction)
187 {
188 // see what kind of node we have
189 if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
190 // if the node is a chardata node, then delete chardata content
191 uint32_t start, numToDelete;
192 if (nsIEditor::eNext == aAction) {
193 start = aOffset;
194 numToDelete = aNode->Length() - aOffset;
195 } else {
196 start = 0;
197 numToDelete = aOffset;
198 }
199
200 if (numToDelete) {
201 RefPtr<nsGenericDOMDataNode> dataNode =
202 static_cast<nsGenericDOMDataNode*>(aNode);
203 RefPtr<DeleteTextTransaction> transaction =
204 new DeleteTextTransaction(*mEditorBase, *dataNode, start, numToDelete,
205 mRangeUpdater);
206
207 nsresult rv = transaction->Init();
208 NS_ENSURE_SUCCESS(rv, rv);
209
210 AppendChild(transaction);
211 }
212 }
213
214 return NS_OK;
215 }
216
217 nsresult
CreateTxnsToDeleteNodesBetween()218 DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
219 {
220 nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
221
222 nsresult rv = iter->Init(mRange);
223 NS_ENSURE_SUCCESS(rv, rv);
224
225 while (!iter->IsDone()) {
226 nsCOMPtr<nsINode> node = iter->GetCurrentNode();
227 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
228
229 RefPtr<DeleteNodeTransaction> transaction = new DeleteNodeTransaction();
230 rv = transaction->Init(mEditorBase, node, mRangeUpdater);
231 NS_ENSURE_SUCCESS(rv, rv);
232 AppendChild(transaction);
233
234 iter->Next();
235 }
236 return NS_OK;
237 }
238
239 } // namespace mozilla
240