Bumping manifests a=b2g-bump
[gecko.git] / editor / libeditor / nsPlaintextDataTransfer.cpp
blob0d042aa6c4a9e418a20e19f8f4cfd8db550304d7
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/. */
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/MouseEvents.h"
8 #include "nsAString.h"
9 #include "nsCOMPtr.h"
10 #include "nsCRT.h"
11 #include "nsComponentManagerUtils.h"
12 #include "nsContentUtils.h"
13 #include "nsDebug.h"
14 #include "nsEditor.h"
15 #include "nsEditorUtils.h"
16 #include "nsError.h"
17 #include "nsIClipboard.h"
18 #include "nsIContent.h"
19 #include "nsIDOMDataTransfer.h"
20 #include "nsIDOMDocument.h"
21 #include "nsIDOMDragEvent.h"
22 #include "nsIDOMEvent.h"
23 #include "nsIDOMNode.h"
24 #include "nsIDOMRange.h"
25 #include "nsIDOMUIEvent.h"
26 #include "nsIDocument.h"
27 #include "nsIDragService.h"
28 #include "nsIDragSession.h"
29 #include "nsIEditor.h"
30 #include "nsIEditorIMESupport.h"
31 #include "nsIDocShell.h"
32 #include "nsIDocShellTreeItem.h"
33 #include "nsIPrincipal.h"
34 #include "nsIFormControl.h"
35 #include "nsIPlaintextEditor.h"
36 #include "nsISelection.h"
37 #include "nsISupportsPrimitives.h"
38 #include "nsITransferable.h"
39 #include "nsIVariant.h"
40 #include "nsLiteralString.h"
41 #include "nsPlaintextEditor.h"
42 #include "nsSelectionState.h"
43 #include "nsServiceManagerUtils.h"
44 #include "nsString.h"
45 #include "nsXPCOM.h"
46 #include "nscore.h"
48 class nsILoadContext;
49 class nsISupports;
51 using namespace mozilla;
52 using namespace mozilla::dom;
54 NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable)
56 // Create generic Transferable for getting the data
57 nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
58 NS_ENSURE_SUCCESS(rv, rv);
60 // Get the nsITransferable interface for getting the data from the clipboard
61 if (transferable) {
62 nsCOMPtr<nsIDocument> destdoc = GetDocument();
63 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
64 (*transferable)->Init(loadContext);
66 (*transferable)->AddDataFlavor(kUnicodeMime);
67 (*transferable)->AddDataFlavor(kMozTextInternal);
69 return NS_OK;
72 nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert,
73 nsIDOMNode *aDestinationNode,
74 int32_t aDestOffset,
75 bool aDoDeleteSelection)
77 if (aDestinationNode)
79 nsresult res;
80 nsCOMPtr<nsISelection>selection;
81 res = GetSelection(getter_AddRefs(selection));
82 NS_ENSURE_SUCCESS(res, res);
84 nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
85 int32_t targetOffset = aDestOffset;
87 if (aDoDeleteSelection)
89 // Use an auto tracker so that our drop point is correctly
90 // positioned after the delete.
91 nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
92 res = DeleteSelection(eNone, eStrip);
93 NS_ENSURE_SUCCESS(res, res);
96 res = selection->Collapse(targetNode, targetOffset);
97 NS_ENSURE_SUCCESS(res, res);
100 return InsertText(aStringToInsert);
103 NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable,
104 nsIDOMNode *aDestinationNode,
105 int32_t aDestOffset,
106 bool aDoDeleteSelection)
108 nsresult rv = NS_OK;
109 char* bestFlavor = nullptr;
110 nsCOMPtr<nsISupports> genericDataObj;
111 uint32_t len = 0;
112 if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len))
113 && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
114 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)))
116 nsAutoTxnsConserveSelection dontSpazMySelection(this);
117 nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
118 if (textDataObj && len > 0)
120 nsAutoString stuffToPaste;
121 textDataObj->GetData(stuffToPaste);
122 NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
124 // Sanitize possible carriage returns in the string to be inserted
125 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
127 nsAutoEditBatch beginBatching(this);
128 rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
131 NS_Free(bestFlavor);
133 // Try to scroll the selection into view if the paste/drop succeeded
135 if (NS_SUCCEEDED(rv))
136 ScrollSelectionIntoView(false);
138 return rv;
141 nsresult nsPlaintextEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer,
142 int32_t aIndex,
143 nsIDOMDocument *aSourceDoc,
144 nsIDOMNode *aDestinationNode,
145 int32_t aDestOffset,
146 bool aDoDeleteSelection)
148 nsCOMPtr<nsIVariant> data;
149 aDataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex,
150 getter_AddRefs(data));
151 if (data) {
152 nsAutoString insertText;
153 data->GetAsAString(insertText);
154 nsContentUtils::PlatformToDOMLineBreaks(insertText);
156 nsAutoEditBatch beginBatching(this);
157 return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
160 return NS_OK;
163 nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
165 ForceCompositionEnd();
167 nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
168 NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
170 nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
171 dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
172 nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
173 NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
175 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
176 NS_ASSERTION(dragSession, "No drag session");
178 nsCOMPtr<nsIDOMNode> sourceNode;
179 dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
181 nsCOMPtr<nsIDOMDocument> srcdomdoc;
182 if (sourceNode) {
183 sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
184 NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
187 if (nsContentUtils::CheckForSubFrameDrop(dragSession,
188 aDropEvent->GetInternalNSEvent()->AsDragEvent())) {
189 // Don't allow drags from subframe documents with different origins than
190 // the drop destination.
191 if (srcdomdoc && !IsSafeToInsertData(srcdomdoc))
192 return NS_OK;
195 // Current doc is destination
196 nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
197 NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
199 uint32_t numItems = 0;
200 nsresult rv = dataTransfer->GetMozItemCount(&numItems);
201 NS_ENSURE_SUCCESS(rv, rv);
202 if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop?
204 // Combine any deletion and drop insertion into one transaction
205 nsAutoEditBatch beginBatching(this);
207 bool deleteSelection = false;
209 // We have to figure out whether to delete and relocate caret only once
210 // Parent and offset are under the mouse cursor
211 nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
212 NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
214 nsCOMPtr<nsIDOMNode> newSelectionParent;
215 rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
216 NS_ENSURE_SUCCESS(rv, rv);
217 NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
219 int32_t newSelectionOffset;
220 rv = uiEvent->GetRangeOffset(&newSelectionOffset);
221 NS_ENSURE_SUCCESS(rv, rv);
223 nsCOMPtr<nsISelection> selection;
224 rv = GetSelection(getter_AddRefs(selection));
225 NS_ENSURE_SUCCESS(rv, rv);
226 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
228 bool isCollapsed = selection->Collapsed();
230 // Only the nsHTMLEditor::FindUserSelectAllNode returns a node.
231 nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
232 if (userSelectNode)
234 // The drop is happening over a "-moz-user-select: all"
235 // subtree so make sure the content we insert goes before
236 // the root of the subtree.
238 // XXX: Note that inserting before the subtree matches the
239 // current behavior when dropping on top of an image.
240 // The decision for dropping before or after the
241 // subtree should really be done based on coordinates.
243 newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset);
245 NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
248 // Check if mouse is in the selection
249 // if so, jump through some hoops to determine if mouse is over selection (bail)
250 // and whether user wants to copy selection or delete it
251 if (!isCollapsed)
253 // We never have to delete if selection is already collapsed
254 bool cursorIsInSelection = false;
256 int32_t rangeCount;
257 rv = selection->GetRangeCount(&rangeCount);
258 NS_ENSURE_SUCCESS(rv, rv);
260 for (int32_t j = 0; j < rangeCount; j++)
262 nsCOMPtr<nsIDOMRange> range;
263 rv = selection->GetRangeAt(j, getter_AddRefs(range));
264 if (NS_FAILED(rv) || !range)
265 continue; // don't bail yet, iterate through them all
267 rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
268 if (cursorIsInSelection)
269 break;
272 if (cursorIsInSelection)
274 // Dragging within same doc can't drop on itself -- leave!
275 if (srcdomdoc == destdomdoc)
276 return NS_OK;
278 // Dragging from another window onto a selection
279 // XXX Decision made to NOT do this,
280 // note that 4.x does replace if dropped on
281 //deleteSelection = true;
283 else
285 // We are NOT over the selection
286 if (srcdomdoc == destdomdoc)
288 // Within the same doc: delete if user doesn't want to copy
289 uint32_t dropEffect;
290 dataTransfer->GetDropEffectInt(&dropEffect);
291 deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
293 else
295 // Different source doc: Don't delete
296 deleteSelection = false;
301 if (IsPlaintextEditor()) {
302 nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
303 while (content) {
304 nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
305 if (formControl && !formControl->AllowDrop()) {
306 // Don't allow dropping into a form control that doesn't allow being
307 // dropped into.
308 return NS_OK;
310 content = content->GetParent();
314 for (uint32_t i = 0; i < numItems; ++i) {
315 InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
316 newSelectionOffset, deleteSelection);
319 if (NS_SUCCEEDED(rv))
320 ScrollSelectionIntoView(false);
322 return rv;
325 NS_IMETHODIMP nsPlaintextEditor::Paste(int32_t aSelectionType)
327 if (!FireClipboardEvent(NS_PASTE, aSelectionType))
328 return NS_OK;
330 // Get Clipboard Service
331 nsresult rv;
332 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
333 if ( NS_FAILED(rv) )
334 return rv;
336 // Get the nsITransferable interface for getting the data from the clipboard
337 nsCOMPtr<nsITransferable> trans;
338 rv = PrepareTransferable(getter_AddRefs(trans));
339 if (NS_SUCCEEDED(rv) && trans)
341 // Get the Data from the clipboard
342 if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
344 // handle transferable hooks
345 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
346 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans))
347 return NS_OK;
349 rv = InsertTextFromTransferable(trans, nullptr, 0, true);
353 return rv;
356 NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable)
358 // Use an invalid value for the clipboard type as data comes from aTransferable
359 // and we don't currently implement a way to put that in the data transfer yet.
360 if (!FireClipboardEvent(NS_PASTE, -1))
361 return NS_OK;
363 if (!IsModifiable())
364 return NS_OK;
366 // handle transferable hooks
367 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
368 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable))
369 return NS_OK;
371 return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
374 NS_IMETHODIMP nsPlaintextEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste)
376 NS_ENSURE_ARG_POINTER(aCanPaste);
377 *aCanPaste = false;
379 // can't paste if readonly
380 if (!IsModifiable())
381 return NS_OK;
383 nsresult rv;
384 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
385 NS_ENSURE_SUCCESS(rv, rv);
387 // the flavors that we can deal with
388 const char* textEditorFlavors[] = { kUnicodeMime };
390 bool haveFlavors;
391 rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
392 ArrayLength(textEditorFlavors),
393 aSelectionType, &haveFlavors);
394 NS_ENSURE_SUCCESS(rv, rv);
396 *aCanPaste = haveFlavors;
397 return NS_OK;
401 NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
403 NS_ENSURE_ARG_POINTER(aCanPaste);
405 // can't paste if readonly
406 if (!IsModifiable()) {
407 *aCanPaste = false;
408 return NS_OK;
411 // If |aTransferable| is null, assume that a paste will succeed.
412 if (!aTransferable) {
413 *aCanPaste = true;
414 return NS_OK;
417 nsCOMPtr<nsISupports> data;
418 uint32_t dataLen;
419 nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
420 getter_AddRefs(data),
421 &dataLen);
422 if (NS_SUCCEEDED(rv) && data)
423 *aCanPaste = true;
424 else
425 *aCanPaste = false;
427 return NS_OK;
430 bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
432 // Try to determine whether we should use a sanitizing fragment sink
433 bool isSafe = false;
435 nsCOMPtr<nsIDocument> destdoc = GetDocument();
436 NS_ASSERTION(destdoc, "Where is our destination doc?");
437 nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
438 nsCOMPtr<nsIDocShellTreeItem> root;
439 if (dsti)
440 dsti->GetRootTreeItem(getter_AddRefs(root));
441 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
442 uint32_t appType;
443 if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType)))
444 isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
445 if (!isSafe && aSourceDoc) {
446 nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
447 NS_ASSERTION(srcdoc, "Where is our source doc?");
449 nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
450 nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
451 NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
452 srcPrincipal->Subsumes(destPrincipal, &isSafe);
455 return isSafe;