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