|
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/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" |
|
47 |
|
48 class nsILoadContext; |
|
49 class nsISupports; |
|
50 |
|
51 using namespace mozilla; |
|
52 using namespace mozilla::dom; |
|
53 |
|
54 NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable) |
|
55 { |
|
56 // Create generic Transferable for getting the data |
|
57 nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable); |
|
58 NS_ENSURE_SUCCESS(rv, rv); |
|
59 |
|
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); |
|
65 |
|
66 (*transferable)->AddDataFlavor(kUnicodeMime); |
|
67 (*transferable)->AddDataFlavor(kMozTextInternal); |
|
68 }; |
|
69 return NS_OK; |
|
70 } |
|
71 |
|
72 nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert, |
|
73 nsIDOMNode *aDestinationNode, |
|
74 int32_t aDestOffset, |
|
75 bool aDoDeleteSelection) |
|
76 { |
|
77 if (aDestinationNode) |
|
78 { |
|
79 nsresult res; |
|
80 nsCOMPtr<nsISelection>selection; |
|
81 res = GetSelection(getter_AddRefs(selection)); |
|
82 NS_ENSURE_SUCCESS(res, res); |
|
83 |
|
84 nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode; |
|
85 int32_t targetOffset = aDestOffset; |
|
86 |
|
87 if (aDoDeleteSelection) |
|
88 { |
|
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); |
|
94 } |
|
95 |
|
96 res = selection->Collapse(targetNode, targetOffset); |
|
97 NS_ENSURE_SUCCESS(res, res); |
|
98 } |
|
99 |
|
100 return InsertText(aStringToInsert); |
|
101 } |
|
102 |
|
103 NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable, |
|
104 nsIDOMNode *aDestinationNode, |
|
105 int32_t aDestOffset, |
|
106 bool aDoDeleteSelection) |
|
107 { |
|
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))) |
|
115 { |
|
116 nsAutoTxnsConserveSelection dontSpazMySelection(this); |
|
117 nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) ); |
|
118 if (textDataObj && len > 0) |
|
119 { |
|
120 nsAutoString stuffToPaste; |
|
121 textDataObj->GetData(stuffToPaste); |
|
122 NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!"); |
|
123 |
|
124 // Sanitize possible carriage returns in the string to be inserted |
|
125 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); |
|
126 |
|
127 nsAutoEditBatch beginBatching(this); |
|
128 rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); |
|
129 } |
|
130 } |
|
131 NS_Free(bestFlavor); |
|
132 |
|
133 // Try to scroll the selection into view if the paste/drop succeeded |
|
134 |
|
135 if (NS_SUCCEEDED(rv)) |
|
136 ScrollSelectionIntoView(false); |
|
137 |
|
138 return rv; |
|
139 } |
|
140 |
|
141 nsresult nsPlaintextEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer, |
|
142 int32_t aIndex, |
|
143 nsIDOMDocument *aSourceDoc, |
|
144 nsIDOMNode *aDestinationNode, |
|
145 int32_t aDestOffset, |
|
146 bool aDoDeleteSelection) |
|
147 { |
|
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); |
|
155 |
|
156 nsAutoEditBatch beginBatching(this); |
|
157 return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection); |
|
158 } |
|
159 |
|
160 return NS_OK; |
|
161 } |
|
162 |
|
163 nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent) |
|
164 { |
|
165 ForceCompositionEnd(); |
|
166 |
|
167 nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent)); |
|
168 NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE); |
|
169 |
|
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); |
|
174 |
|
175 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); |
|
176 NS_ASSERTION(dragSession, "No drag session"); |
|
177 |
|
178 nsCOMPtr<nsIDOMNode> sourceNode; |
|
179 dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode)); |
|
180 |
|
181 nsCOMPtr<nsIDOMDocument> srcdomdoc; |
|
182 if (sourceNode) { |
|
183 sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc)); |
|
184 NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE); |
|
185 } |
|
186 |
|
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; |
|
193 } |
|
194 |
|
195 // Current doc is destination |
|
196 nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument(); |
|
197 NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED); |
|
198 |
|
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? |
|
203 |
|
204 // Combine any deletion and drop insertion into one transaction |
|
205 nsAutoEditBatch beginBatching(this); |
|
206 |
|
207 bool deleteSelection = false; |
|
208 |
|
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); |
|
213 |
|
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); |
|
218 |
|
219 int32_t newSelectionOffset; |
|
220 rv = uiEvent->GetRangeOffset(&newSelectionOffset); |
|
221 NS_ENSURE_SUCCESS(rv, rv); |
|
222 |
|
223 nsCOMPtr<nsISelection> selection; |
|
224 rv = GetSelection(getter_AddRefs(selection)); |
|
225 NS_ENSURE_SUCCESS(rv, rv); |
|
226 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
227 |
|
228 bool isCollapsed = selection->Collapsed(); |
|
229 |
|
230 // Only the nsHTMLEditor::FindUserSelectAllNode returns a node. |
|
231 nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent); |
|
232 if (userSelectNode) |
|
233 { |
|
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. |
|
237 // |
|
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. |
|
242 |
|
243 newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset); |
|
244 |
|
245 NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE); |
|
246 } |
|
247 |
|
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) |
|
252 { |
|
253 // We never have to delete if selection is already collapsed |
|
254 bool cursorIsInSelection = false; |
|
255 |
|
256 int32_t rangeCount; |
|
257 rv = selection->GetRangeCount(&rangeCount); |
|
258 NS_ENSURE_SUCCESS(rv, rv); |
|
259 |
|
260 for (int32_t j = 0; j < rangeCount; j++) |
|
261 { |
|
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 |
|
266 |
|
267 rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection); |
|
268 if (cursorIsInSelection) |
|
269 break; |
|
270 } |
|
271 |
|
272 if (cursorIsInSelection) |
|
273 { |
|
274 // Dragging within same doc can't drop on itself -- leave! |
|
275 if (srcdomdoc == destdomdoc) |
|
276 return NS_OK; |
|
277 |
|
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; |
|
282 } |
|
283 else |
|
284 { |
|
285 // We are NOT over the selection |
|
286 if (srcdomdoc == destdomdoc) |
|
287 { |
|
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); |
|
292 } |
|
293 else |
|
294 { |
|
295 // Different source doc: Don't delete |
|
296 deleteSelection = false; |
|
297 } |
|
298 } |
|
299 } |
|
300 |
|
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; |
|
309 } |
|
310 content = content->GetParent(); |
|
311 } |
|
312 } |
|
313 |
|
314 for (uint32_t i = 0; i < numItems; ++i) { |
|
315 InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent, |
|
316 newSelectionOffset, deleteSelection); |
|
317 } |
|
318 |
|
319 if (NS_SUCCEEDED(rv)) |
|
320 ScrollSelectionIntoView(false); |
|
321 |
|
322 return rv; |
|
323 } |
|
324 |
|
325 NS_IMETHODIMP nsPlaintextEditor::Paste(int32_t aSelectionType) |
|
326 { |
|
327 if (!FireClipboardEvent(NS_PASTE, aSelectionType)) |
|
328 return NS_OK; |
|
329 |
|
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; |
|
335 |
|
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) |
|
340 { |
|
341 // Get the Data from the clipboard |
|
342 if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable()) |
|
343 { |
|
344 // handle transferable hooks |
|
345 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument(); |
|
346 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) |
|
347 return NS_OK; |
|
348 |
|
349 rv = InsertTextFromTransferable(trans, nullptr, 0, true); |
|
350 } |
|
351 } |
|
352 |
|
353 return rv; |
|
354 } |
|
355 |
|
356 NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable) |
|
357 { |
|
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; |
|
362 |
|
363 if (!IsModifiable()) |
|
364 return NS_OK; |
|
365 |
|
366 // handle transferable hooks |
|
367 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument(); |
|
368 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) |
|
369 return NS_OK; |
|
370 |
|
371 return InsertTextFromTransferable(aTransferable, nullptr, 0, true); |
|
372 } |
|
373 |
|
374 NS_IMETHODIMP nsPlaintextEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) |
|
375 { |
|
376 NS_ENSURE_ARG_POINTER(aCanPaste); |
|
377 *aCanPaste = false; |
|
378 |
|
379 // can't paste if readonly |
|
380 if (!IsModifiable()) |
|
381 return NS_OK; |
|
382 |
|
383 nsresult rv; |
|
384 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); |
|
385 NS_ENSURE_SUCCESS(rv, rv); |
|
386 |
|
387 // the flavors that we can deal with |
|
388 const char* textEditorFlavors[] = { kUnicodeMime }; |
|
389 |
|
390 bool haveFlavors; |
|
391 rv = clipboard->HasDataMatchingFlavors(textEditorFlavors, |
|
392 ArrayLength(textEditorFlavors), |
|
393 aSelectionType, &haveFlavors); |
|
394 NS_ENSURE_SUCCESS(rv, rv); |
|
395 |
|
396 *aCanPaste = haveFlavors; |
|
397 return NS_OK; |
|
398 } |
|
399 |
|
400 |
|
401 NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) |
|
402 { |
|
403 NS_ENSURE_ARG_POINTER(aCanPaste); |
|
404 |
|
405 // can't paste if readonly |
|
406 if (!IsModifiable()) { |
|
407 *aCanPaste = false; |
|
408 return NS_OK; |
|
409 } |
|
410 |
|
411 // If |aTransferable| is null, assume that a paste will succeed. |
|
412 if (!aTransferable) { |
|
413 *aCanPaste = true; |
|
414 return NS_OK; |
|
415 } |
|
416 |
|
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; |
|
426 |
|
427 return NS_OK; |
|
428 } |
|
429 |
|
430 bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc) |
|
431 { |
|
432 // Try to determine whether we should use a sanitizing fragment sink |
|
433 bool isSafe = false; |
|
434 |
|
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?"); |
|
448 |
|
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); |
|
453 } |
|
454 |
|
455 return isSafe; |
|
456 } |
|
457 |