/src/mozilla-central/editor/libeditor/TextEditorDataTransfer.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 "mozilla/TextEditor.h" |
7 | | |
8 | | #include "mozilla/ArrayUtils.h" |
9 | | #include "mozilla/EditorUtils.h" |
10 | | #include "mozilla/MouseEvents.h" |
11 | | #include "mozilla/SelectionState.h" |
12 | | #include "mozilla/dom/DataTransfer.h" |
13 | | #include "mozilla/dom/DragEvent.h" |
14 | | #include "mozilla/dom/Selection.h" |
15 | | #include "nsAString.h" |
16 | | #include "nsCOMPtr.h" |
17 | | #include "nsComponentManagerUtils.h" |
18 | | #include "nsContentUtils.h" |
19 | | #include "nsDebug.h" |
20 | | #include "nsError.h" |
21 | | #include "nsIClipboard.h" |
22 | | #include "nsIContent.h" |
23 | | #include "nsIDocument.h" |
24 | | #include "nsIDragService.h" |
25 | | #include "nsIDragSession.h" |
26 | | #include "nsIEditor.h" |
27 | | #include "nsIDocShell.h" |
28 | | #include "nsIDocShellTreeItem.h" |
29 | | #include "nsIPrincipal.h" |
30 | | #include "nsIFormControl.h" |
31 | | #include "nsIPlaintextEditor.h" |
32 | | #include "nsISupportsPrimitives.h" |
33 | | #include "nsITransferable.h" |
34 | | #include "nsIVariant.h" |
35 | | #include "nsLiteralString.h" |
36 | | #include "nsRange.h" |
37 | | #include "nsServiceManagerUtils.h" |
38 | | #include "nsString.h" |
39 | | #include "nsXPCOM.h" |
40 | | #include "nscore.h" |
41 | | |
42 | | class nsILoadContext; |
43 | | class nsISupports; |
44 | | |
45 | | namespace mozilla { |
46 | | |
47 | | using namespace dom; |
48 | | |
49 | | nsresult |
50 | | TextEditor::PrepareTransferable(nsITransferable** transferable) |
51 | 0 | { |
52 | 0 | // Create generic Transferable for getting the data |
53 | 0 | nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable); |
54 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
55 | 0 |
|
56 | 0 | // Get the nsITransferable interface for getting the data from the clipboard |
57 | 0 | if (transferable) { |
58 | 0 | nsCOMPtr<nsIDocument> destdoc = GetDocument(); |
59 | 0 | nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; |
60 | 0 | (*transferable)->Init(loadContext); |
61 | 0 |
|
62 | 0 | (*transferable)->AddDataFlavor(kUnicodeMime); |
63 | 0 | (*transferable)->AddDataFlavor(kMozTextInternal); |
64 | 0 | }; |
65 | 0 | return NS_OK; |
66 | 0 | } |
67 | | |
68 | | nsresult |
69 | | TextEditor::InsertTextAt(const nsAString& aStringToInsert, |
70 | | nsINode* aDestinationNode, |
71 | | int32_t aDestOffset, |
72 | | bool aDoDeleteSelection) |
73 | 0 | { |
74 | 0 | if (aDestinationNode) { |
75 | 0 | RefPtr<Selection> selection = GetSelection(); |
76 | 0 | NS_ENSURE_STATE(selection); |
77 | 0 |
|
78 | 0 | nsCOMPtr<nsINode> targetNode = aDestinationNode; |
79 | 0 | int32_t targetOffset = aDestOffset; |
80 | 0 |
|
81 | 0 | if (aDoDeleteSelection) { |
82 | 0 | // Use an auto tracker so that our drop point is correctly |
83 | 0 | // positioned after the delete. |
84 | 0 | AutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset); |
85 | 0 | nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); |
86 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
87 | 0 | return rv; |
88 | 0 | } |
89 | 0 | } |
90 | 0 | |
91 | 0 | ErrorResult error; |
92 | 0 | selection->Collapse(RawRangeBoundary(targetNode, targetOffset), error); |
93 | 0 | if (NS_WARN_IF(error.Failed())) { |
94 | 0 | return error.StealNSResult(); |
95 | 0 | } |
96 | 0 | } |
97 | 0 | |
98 | 0 | nsresult rv = InsertTextAsSubAction(aStringToInsert); |
99 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
100 | 0 | return rv; |
101 | 0 | } |
102 | 0 | return NS_OK; |
103 | 0 | } |
104 | | |
105 | | nsresult |
106 | | TextEditor::InsertTextFromTransferable(nsITransferable* aTransferable) |
107 | 0 | { |
108 | 0 | nsresult rv = NS_OK; |
109 | 0 | nsAutoCString bestFlavor; |
110 | 0 | nsCOMPtr<nsISupports> genericDataObj; |
111 | 0 | uint32_t len = 0; |
112 | 0 | if (NS_SUCCEEDED( |
113 | 0 | aTransferable->GetAnyTransferData(bestFlavor, |
114 | 0 | getter_AddRefs(genericDataObj), |
115 | 0 | &len)) && |
116 | 0 | (bestFlavor.EqualsLiteral(kUnicodeMime) || |
117 | 0 | bestFlavor.EqualsLiteral(kMozTextInternal))) { |
118 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(*this); |
119 | 0 | nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) ); |
120 | 0 | if (textDataObj && len > 0) { |
121 | 0 | nsAutoString stuffToPaste; |
122 | 0 | textDataObj->GetData(stuffToPaste); |
123 | 0 | NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!"); |
124 | 0 |
|
125 | 0 | // Sanitize possible carriage returns in the string to be inserted |
126 | 0 | nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); |
127 | 0 |
|
128 | 0 | AutoPlaceholderBatch beginBatching(this); |
129 | 0 | rv = InsertTextAt(stuffToPaste, nullptr, 0, true); |
130 | 0 | } |
131 | 0 | } |
132 | 0 |
|
133 | 0 | // Try to scroll the selection into view if the paste/drop succeeded |
134 | 0 |
|
135 | 0 | if (NS_SUCCEEDED(rv)) { |
136 | 0 | ScrollSelectionIntoView(false); |
137 | 0 | } |
138 | 0 |
|
139 | 0 | return rv; |
140 | 0 | } |
141 | | |
142 | | nsresult |
143 | | TextEditor::InsertFromDataTransfer(DataTransfer* aDataTransfer, |
144 | | int32_t aIndex, |
145 | | nsIDocument* aSourceDoc, |
146 | | nsINode* aDestinationNode, |
147 | | int32_t aDestOffset, |
148 | | bool aDoDeleteSelection) |
149 | 0 | { |
150 | 0 | nsCOMPtr<nsIVariant> data; |
151 | 0 | aDataTransfer->GetDataAtNoSecurityCheck(NS_LITERAL_STRING("text/plain"), aIndex, |
152 | 0 | getter_AddRefs(data)); |
153 | 0 | if (data) { |
154 | 0 | nsAutoString insertText; |
155 | 0 | data->GetAsAString(insertText); |
156 | 0 | nsContentUtils::PlatformToDOMLineBreaks(insertText); |
157 | 0 |
|
158 | 0 | AutoPlaceholderBatch beginBatching(this); |
159 | 0 | return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection); |
160 | 0 | } |
161 | 0 | |
162 | 0 | return NS_OK; |
163 | 0 | } |
164 | | |
165 | | nsresult |
166 | | TextEditor::OnDrop(DragEvent* aDropEvent) |
167 | 0 | { |
168 | 0 | CommitComposition(); |
169 | 0 |
|
170 | 0 | NS_ENSURE_TRUE(aDropEvent, NS_ERROR_FAILURE); |
171 | 0 |
|
172 | 0 | RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer(); |
173 | 0 | NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE); |
174 | 0 |
|
175 | 0 | nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); |
176 | 0 | NS_ASSERTION(dragSession, "No drag session"); |
177 | 0 |
|
178 | 0 | nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode(); |
179 | 0 |
|
180 | 0 | nsCOMPtr<nsIDocument> srcdoc; |
181 | 0 | if (sourceNode) { |
182 | 0 | srcdoc = sourceNode->OwnerDoc(); |
183 | 0 | } |
184 | 0 |
|
185 | 0 | if (nsContentUtils::CheckForSubFrameDrop(dragSession, |
186 | 0 | aDropEvent->WidgetEventPtr()->AsDragEvent())) { |
187 | 0 | // Don't allow drags from subframe documents with different origins than |
188 | 0 | // the drop destination. |
189 | 0 | if (srcdoc && !IsSafeToInsertData(srcdoc)) { |
190 | 0 | return NS_OK; |
191 | 0 | } |
192 | 0 | } |
193 | 0 | |
194 | 0 | // Current doc is destination |
195 | 0 | nsIDocument* destdoc = GetDocument(); |
196 | 0 | if (NS_WARN_IF(!destdoc)) { |
197 | 0 | return NS_ERROR_NOT_INITIALIZED; |
198 | 0 | } |
199 | 0 | |
200 | 0 | uint32_t numItems = dataTransfer->MozItemCount(); |
201 | 0 | if (numItems < 1) { |
202 | 0 | return NS_ERROR_FAILURE; // Nothing to drop? |
203 | 0 | } |
204 | 0 | |
205 | 0 | // Combine any deletion and drop insertion into one transaction |
206 | 0 | AutoPlaceholderBatch beginBatching(this); |
207 | 0 |
|
208 | 0 | bool deleteSelection = false; |
209 | 0 |
|
210 | 0 | // We have to figure out whether to delete and relocate caret only once |
211 | 0 | // Parent and offset are under the mouse cursor |
212 | 0 | nsCOMPtr<nsINode> newSelectionParent = aDropEvent->GetRangeParent(); |
213 | 0 | NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); |
214 | 0 |
|
215 | 0 | int32_t newSelectionOffset = aDropEvent->RangeOffset(); |
216 | 0 |
|
217 | 0 | RefPtr<Selection> selection = GetSelection(); |
218 | 0 | NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
219 | 0 |
|
220 | 0 | bool isCollapsed = selection->IsCollapsed(); |
221 | 0 |
|
222 | 0 | // Check if mouse is in the selection |
223 | 0 | // if so, jump through some hoops to determine if mouse is over selection (bail) |
224 | 0 | // and whether user wants to copy selection or delete it |
225 | 0 | if (!isCollapsed) { |
226 | 0 | // We never have to delete if selection is already collapsed |
227 | 0 | bool cursorIsInSelection = false; |
228 | 0 |
|
229 | 0 | uint32_t rangeCount = selection->RangeCount(); |
230 | 0 |
|
231 | 0 | for (uint32_t j = 0; j < rangeCount; j++) { |
232 | 0 | RefPtr<nsRange> range = selection->GetRangeAt(j); |
233 | 0 | if (!range) { |
234 | 0 | // don't bail yet, iterate through them all |
235 | 0 | continue; |
236 | 0 | } |
237 | 0 | |
238 | 0 | IgnoredErrorResult rv; |
239 | 0 | cursorIsInSelection = |
240 | 0 | range->IsPointInRange(*newSelectionParent, newSelectionOffset, rv); |
241 | 0 | if (rv.Failed()) { |
242 | 0 | // Probably don't want to consider this as "in selection!" |
243 | 0 | cursorIsInSelection = false; |
244 | 0 | } |
245 | 0 | if (cursorIsInSelection) { |
246 | 0 | break; |
247 | 0 | } |
248 | 0 | } |
249 | 0 |
|
250 | 0 | if (cursorIsInSelection) { |
251 | 0 | // Dragging within same doc can't drop on itself -- leave! |
252 | 0 | if (srcdoc == destdoc) { |
253 | 0 | return NS_OK; |
254 | 0 | } |
255 | 0 | |
256 | 0 | // Dragging from another window onto a selection |
257 | 0 | // XXX Decision made to NOT do this, |
258 | 0 | // note that 4.x does replace if dropped on |
259 | 0 | //deleteSelection = true; |
260 | 0 | } else { |
261 | 0 | // We are NOT over the selection |
262 | 0 | if (srcdoc == destdoc) { |
263 | 0 | // Within the same doc: delete if user doesn't want to copy |
264 | 0 | uint32_t dropEffect = dataTransfer->DropEffectInt(); |
265 | 0 | deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY); |
266 | 0 | } else { |
267 | 0 | // Different source doc: Don't delete |
268 | 0 | deleteSelection = false; |
269 | 0 | } |
270 | 0 | } |
271 | 0 | } |
272 | 0 |
|
273 | 0 | if (IsPlaintextEditor()) { |
274 | 0 | nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent); |
275 | 0 | while (content) { |
276 | 0 | nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content)); |
277 | 0 | if (formControl && !formControl->AllowDrop()) { |
278 | 0 | // Don't allow dropping into a form control that doesn't allow being |
279 | 0 | // dropped into. |
280 | 0 | return NS_OK; |
281 | 0 | } |
282 | 0 | content = content->GetParent(); |
283 | 0 | } |
284 | 0 | } |
285 | 0 |
|
286 | 0 | for (uint32_t i = 0; i < numItems; ++i) { |
287 | 0 | InsertFromDataTransfer(dataTransfer, i, srcdoc, |
288 | 0 | newSelectionParent, |
289 | 0 | newSelectionOffset, deleteSelection); |
290 | 0 | } |
291 | 0 |
|
292 | 0 | ScrollSelectionIntoView(false); |
293 | 0 |
|
294 | 0 | return NS_OK; |
295 | 0 | } |
296 | | |
297 | | nsresult |
298 | | TextEditor::PasteAsAction(int32_t aClipboardType) |
299 | 0 | { |
300 | 0 | if (AsHTMLEditor()) { |
301 | 0 | nsresult rv = AsHTMLEditor()->PasteInternal(aClipboardType); |
302 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
303 | 0 | return rv; |
304 | 0 | } |
305 | 0 | return NS_OK; |
306 | 0 | } |
307 | 0 | |
308 | 0 | if (!FireClipboardEvent(ePaste, aClipboardType)) { |
309 | 0 | return NS_OK; |
310 | 0 | } |
311 | 0 | |
312 | 0 | // Get Clipboard Service |
313 | 0 | nsresult rv; |
314 | 0 | nsCOMPtr<nsIClipboard> clipboard = |
315 | 0 | do_GetService("@mozilla.org/widget/clipboard;1", &rv); |
316 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
317 | 0 | return rv; |
318 | 0 | } |
319 | 0 | |
320 | 0 | // Get the nsITransferable interface for getting the data from the clipboard |
321 | 0 | nsCOMPtr<nsITransferable> transferable; |
322 | 0 | rv = PrepareTransferable(getter_AddRefs(transferable)); |
323 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
324 | 0 | return rv; |
325 | 0 | } |
326 | 0 | if (NS_WARN_IF(!transferable)) { |
327 | 0 | return NS_OK; // XXX Why? |
328 | 0 | } |
329 | 0 | // Get the Data from the clipboard. |
330 | 0 | rv = clipboard->GetData(transferable, aClipboardType); |
331 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
332 | 0 | return NS_OK; // XXX Why? |
333 | 0 | } |
334 | 0 | // XXX Why don't we check this first? |
335 | 0 | if (!IsModifiable()) { |
336 | 0 | return NS_OK; |
337 | 0 | } |
338 | 0 | rv = InsertTextFromTransferable(transferable); |
339 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
340 | 0 | return rv; |
341 | 0 | } |
342 | 0 | return NS_OK; |
343 | 0 | } |
344 | | |
345 | | NS_IMETHODIMP |
346 | | TextEditor::PasteTransferable(nsITransferable* aTransferable) |
347 | 0 | { |
348 | 0 | // Use an invalid value for the clipboard type as data comes from aTransferable |
349 | 0 | // and we don't currently implement a way to put that in the data transfer yet. |
350 | 0 | if (!FireClipboardEvent(ePaste, -1)) { |
351 | 0 | return NS_OK; |
352 | 0 | } |
353 | 0 | |
354 | 0 | if (!IsModifiable()) { |
355 | 0 | return NS_OK; |
356 | 0 | } |
357 | 0 | |
358 | 0 | return InsertTextFromTransferable(aTransferable); |
359 | 0 | } |
360 | | |
361 | | NS_IMETHODIMP |
362 | | TextEditor::CanPaste(int32_t aSelectionType, |
363 | | bool* aCanPaste) |
364 | 0 | { |
365 | 0 | NS_ENSURE_ARG_POINTER(aCanPaste); |
366 | 0 | *aCanPaste = false; |
367 | 0 |
|
368 | 0 | // Always enable the paste command when inside of a HTML or XHTML document. |
369 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
370 | 0 | if (doc && doc->IsHTMLOrXHTML()) { |
371 | 0 | *aCanPaste = true; |
372 | 0 | return NS_OK; |
373 | 0 | } |
374 | 0 | |
375 | 0 | // can't paste if readonly |
376 | 0 | if (!IsModifiable()) { |
377 | 0 | return NS_OK; |
378 | 0 | } |
379 | 0 | |
380 | 0 | nsresult rv; |
381 | 0 | nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); |
382 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
383 | 0 |
|
384 | 0 | // the flavors that we can deal with |
385 | 0 | const char* textEditorFlavors[] = { kUnicodeMime }; |
386 | 0 |
|
387 | 0 | bool haveFlavors; |
388 | 0 | rv = clipboard->HasDataMatchingFlavors(textEditorFlavors, |
389 | 0 | ArrayLength(textEditorFlavors), |
390 | 0 | aSelectionType, &haveFlavors); |
391 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
392 | 0 |
|
393 | 0 | *aCanPaste = haveFlavors; |
394 | 0 | return NS_OK; |
395 | 0 | } |
396 | | |
397 | | bool |
398 | | TextEditor::CanPasteTransferable(nsITransferable* aTransferable) |
399 | 0 | { |
400 | 0 | // can't paste if readonly |
401 | 0 | if (!IsModifiable()) { |
402 | 0 | return false; |
403 | 0 | } |
404 | 0 | |
405 | 0 | // If |aTransferable| is null, assume that a paste will succeed. |
406 | 0 | if (!aTransferable) { |
407 | 0 | return true; |
408 | 0 | } |
409 | 0 | |
410 | 0 | nsCOMPtr<nsISupports> data; |
411 | 0 | uint32_t dataLen; |
412 | 0 | nsresult rv = aTransferable->GetTransferData(kUnicodeMime, |
413 | 0 | getter_AddRefs(data), |
414 | 0 | &dataLen); |
415 | 0 | if (NS_SUCCEEDED(rv) && data) { |
416 | 0 | return true; |
417 | 0 | } |
418 | 0 | |
419 | 0 | return false; |
420 | 0 | } |
421 | | |
422 | | bool |
423 | | TextEditor::IsSafeToInsertData(nsIDocument* aSourceDoc) |
424 | 0 | { |
425 | 0 | // Try to determine whether we should use a sanitizing fragment sink |
426 | 0 | bool isSafe = false; |
427 | 0 |
|
428 | 0 | nsCOMPtr<nsIDocument> destdoc = GetDocument(); |
429 | 0 | NS_ASSERTION(destdoc, "Where is our destination doc?"); |
430 | 0 | nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell(); |
431 | 0 | nsCOMPtr<nsIDocShellTreeItem> root; |
432 | 0 | if (dsti) { |
433 | 0 | dsti->GetRootTreeItem(getter_AddRefs(root)); |
434 | 0 | } |
435 | 0 | nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root); |
436 | 0 | uint32_t appType; |
437 | 0 | if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType))) { |
438 | 0 | isSafe = appType == nsIDocShell::APP_TYPE_EDITOR; |
439 | 0 | } |
440 | 0 | if (!isSafe && aSourceDoc) { |
441 | 0 | nsIPrincipal* srcPrincipal = aSourceDoc->NodePrincipal(); |
442 | 0 | nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); |
443 | 0 | NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?"); |
444 | 0 | srcPrincipal->Subsumes(destPrincipal, &isSafe); |
445 | 0 | } |
446 | 0 |
|
447 | 0 | return isSafe; |
448 | 0 | } |
449 | | |
450 | | } // namespace mozilla |