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"
11 #include "nsComponentManagerUtils.h"
12 #include "nsContentUtils.h"
15 #include "nsEditorUtils.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"
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
62 nsCOMPtr
<nsIDocument
> destdoc
= GetDocument();
63 nsILoadContext
* loadContext
= destdoc
? destdoc
->GetLoadContext() : nullptr;
64 (*transferable
)->Init(loadContext
);
66 (*transferable
)->AddDataFlavor(kUnicodeMime
);
67 (*transferable
)->AddDataFlavor(kMozTextInternal
);
72 nsresult
nsPlaintextEditor::InsertTextAt(const nsAString
&aStringToInsert
,
73 nsIDOMNode
*aDestinationNode
,
75 bool aDoDeleteSelection
)
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
,
106 bool aDoDeleteSelection
)
109 char* bestFlavor
= nullptr;
110 nsCOMPtr
<nsISupports
> genericDataObj
;
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
);
133 // Try to scroll the selection into view if the paste/drop succeeded
135 if (NS_SUCCEEDED(rv
))
136 ScrollSelectionIntoView(false);
141 nsresult
nsPlaintextEditor::InsertFromDataTransfer(DataTransfer
*aDataTransfer
,
143 nsIDOMDocument
*aSourceDoc
,
144 nsIDOMNode
*aDestinationNode
,
146 bool aDoDeleteSelection
)
148 nsCOMPtr
<nsIVariant
> data
;
149 aDataTransfer
->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex
,
150 getter_AddRefs(data
));
152 nsAutoString insertText
;
153 data
->GetAsAString(insertText
);
154 nsContentUtils::PlatformToDOMLineBreaks(insertText
);
156 nsAutoEditBatch
beginBatching(this);
157 return InsertTextAt(insertText
, aDestinationNode
, aDestOffset
, aDoDeleteSelection
);
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
;
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
))
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
);
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
253 // We never have to delete if selection is already collapsed
254 bool cursorIsInSelection
= false;
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
)
272 if (cursorIsInSelection
)
274 // Dragging within same doc can't drop on itself -- leave!
275 if (srcdomdoc
== destdomdoc
)
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;
285 // We are NOT over the selection
286 if (srcdomdoc
== destdomdoc
)
288 // Within the same doc: delete if user doesn't want to copy
290 dataTransfer
->GetDropEffectInt(&dropEffect
);
291 deleteSelection
= !(dropEffect
& nsIDragService::DRAGDROP_ACTION_COPY
);
295 // Different source doc: Don't delete
296 deleteSelection
= false;
301 if (IsPlaintextEditor()) {
302 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(newSelectionParent
);
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
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);
325 NS_IMETHODIMP
nsPlaintextEditor::Paste(int32_t aSelectionType
)
327 if (!FireClipboardEvent(NS_PASTE
, aSelectionType
))
330 // Get Clipboard Service
332 nsCOMPtr
<nsIClipboard
> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &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
))
349 rv
= InsertTextFromTransferable(trans
, nullptr, 0, true);
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))
366 // handle transferable hooks
367 nsCOMPtr
<nsIDOMDocument
> domdoc
= GetDOMDocument();
368 if (!nsEditorHookUtils::DoInsertionHook(domdoc
, nullptr, aTransferable
))
371 return InsertTextFromTransferable(aTransferable
, nullptr, 0, true);
374 NS_IMETHODIMP
nsPlaintextEditor::CanPaste(int32_t aSelectionType
, bool *aCanPaste
)
376 NS_ENSURE_ARG_POINTER(aCanPaste
);
379 // can't paste if readonly
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
};
391 rv
= clipboard
->HasDataMatchingFlavors(textEditorFlavors
,
392 ArrayLength(textEditorFlavors
),
393 aSelectionType
, &haveFlavors
);
394 NS_ENSURE_SUCCESS(rv
, rv
);
396 *aCanPaste
= haveFlavors
;
401 NS_IMETHODIMP
nsPlaintextEditor::CanPasteTransferable(nsITransferable
*aTransferable
, bool *aCanPaste
)
403 NS_ENSURE_ARG_POINTER(aCanPaste
);
405 // can't paste if readonly
406 if (!IsModifiable()) {
411 // If |aTransferable| is null, assume that a paste will succeed.
412 if (!aTransferable
) {
417 nsCOMPtr
<nsISupports
> data
;
419 nsresult rv
= aTransferable
->GetTransferData(kUnicodeMime
,
420 getter_AddRefs(data
),
422 if (NS_SUCCEEDED(rv
) && data
)
430 bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument
* aSourceDoc
)
432 // Try to determine whether we should use a sanitizing fragment sink
435 nsCOMPtr
<nsIDocument
> destdoc
= GetDocument();
436 NS_ASSERTION(destdoc
, "Where is our destination doc?");
437 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= destdoc
->GetDocShell();
438 nsCOMPtr
<nsIDocShellTreeItem
> root
;
440 dsti
->GetRootTreeItem(getter_AddRefs(root
));
441 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(root
);
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
);