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